public inbox for [email protected]
help / color / mirror / Atom feedRe: PATCH: Make pg_stop_backup() archive wait optional
52+ messages / 5 participants
[nested] [flat]
* Re: PATCH: Make pg_stop_backup() archive wait optional
@ 2017-04-04 15:42 Stephen Frost <[email protected]>
2017-04-04 16:23 ` Re: PATCH: Make pg_stop_backup() archive wait optional David Steele <[email protected]>
0 siblings, 1 reply; 52+ messages in thread
From: Stephen Frost @ 2017-04-04 15:42 UTC (permalink / raw)
To: David Steele <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; Fujii Masao <[email protected]>; Tsunakawa, Takayuki <[email protected]>; Michael Paquier <[email protected]>; pgsql-hackers
* David Steele ([email protected]) wrote:
> On 3/22/17 4:42 PM, Peter Eisentraut wrote:
> >On 3/22/17 15:14, Stephen Frost wrote:
> >>>-SELECT * FROM pg_stop_backup(false);
> >>>+SELECT * FROM pg_stop_backup(false [, true ]);
> >>>
> >>>I think that it's better to get rid of "[" and "]" from the above because
> >>>IMO this should be the command example that users actually can run.
> >>Using the '[' and ']' are how all of the optional arguments are
> >>specified in the documentation, see things like current_setting() in our
> >>existing documentation:
> >
> >In the synopsis, but not in concrete examples.
>
> Wasn't quite sure what the protocol was in this case, and figured it
> was easier to subtract than to add.
Forgot to close this out sooner, apologies.
This has been committed now.
Thanks!
Stephen
Attachments:
[application/pgp-signature] signature.asc (819B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 52+ messages in thread
* Re: PATCH: Make pg_stop_backup() archive wait optional
2017-04-04 15:42 Re: PATCH: Make pg_stop_backup() archive wait optional Stephen Frost <[email protected]>
@ 2017-04-04 16:23 ` David Steele <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: David Steele @ 2017-04-04 16:23 UTC (permalink / raw)
To: pgsql-hackers
On 4/4/17 11:42 AM, Stephen Frost wrote:
> * David Steele ([email protected]) wrote:
>> On 3/22/17 4:42 PM, Peter Eisentraut wrote:
>>> On 3/22/17 15:14, Stephen Frost wrote:
>>>>> -SELECT * FROM pg_stop_backup(false);
>>>>> +SELECT * FROM pg_stop_backup(false [, true ]);
>>>>>
>>>>> I think that it's better to get rid of "[" and "]" from the above because
>>>>> IMO this should be the command example that users actually can run.
>>>> Using the '[' and ']' are how all of the optional arguments are
>>>> specified in the documentation, see things like current_setting() in our
>>>> existing documentation:
>>>
>>> In the synopsis, but not in concrete examples.
>>
>> Wasn't quite sure what the protocol was in this case, and figured it
>> was easier to subtract than to add.
>
> Forgot to close this out sooner, apologies.
>
> This has been committed now.
Thank you, Stephen!
--
-David
[email protected]
Attachments:
[application/pgp-signature] signature.asc (842B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 43 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b7150510ab..9a3f79e3b7 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..b10bd04ec8 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = &PG_GETARG_DATUM(0);
+ nulls = &PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ /*
+ * only items that match the queried element
+ * are considered candidate
+ */
+
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* we will need recheck */
*recheck = true;
@@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
}
break;
+ case GinContainsElemStrategy:
case GinContainedStrategy:
/* can't do anything else useful here */
res = GIN_MAYBE;
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index f7012cc5d9..f8cbf64c9e 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 0d4eac8f96..7ef071135c 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e0c9be58c..8bc05707c7 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8180,6 +8180,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 8bc7721e7d..95c9ae5443 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..698d322e14 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 3
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 0
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index c40619a8d5..b5eec945f7 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.17.0
--opg8F0UgoHELSI+9
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment; filename="0002-fix.patch"
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw)
---
doc/src/sgml/func.sgml | 28 +++++
doc/src/sgml/gin.sgml | 8 +-
doc/src/sgml/indices.sgml | 2 +-
src/backend/access/gin/ginarrayproc.c | 40 +++++--
src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++
src/include/catalog/pg_amop.dat | 3 +
src/include/catalog/pg_operator.dat | 14 ++-
src/include/catalog/pg_proc.dat | 6 +
src/test/regress/expected/arrays.out | 92 +++++++++++++++
src/test/regress/expected/gin.out | 34 ++++++
src/test/regress/expected/opr_sanity.out | 6 +-
src/test/regress/sql/arrays.sql | 10 ++
src/test/regress/sql/gin.sql | 8 ++
13 files changed, 372 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9492a3c6b9..04216e96a3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ...
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyarray</type> <literal>@>></literal> <type>anyelement</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Does the array contain the specified element?
+ </para>
+ <para>
+ <literal>ARRAY[1,4,3] @>> 3</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <type>anyelement</type> <literal><<@</literal> <type>anyarray</type>
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Is the specified element contained in the array?
+ </para>
+ <para>
+ <literal>2 <<@ ARRAY[1,7,4,2,6]</literal>
+ <returnvalue>t</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<type>anyarray</type> <literal>&&</literal> <type>anyarray</type>
diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml
index d68d12d515..981513b765 100644
--- a/doc/src/sgml/gin.sgml
+++ b/doc/src/sgml/gin.sgml
@@ -84,7 +84,7 @@
</thead>
<tbody>
<row>
- <entry morerows="3" valign="middle"><literal>array_ops</literal></entry>
+ <entry morerows="5" valign="middle"><literal>array_ops</literal></entry>
<entry><literal>&& (anyarray,anyarray)</literal></entry>
</row>
<row>
@@ -93,6 +93,12 @@
<row>
<entry><literal><@ (anyarray,anyarray)</literal></entry>
</row>
+ <row>
+ <entry><literal>@>> (anyarray,anyelement)</literal></entry>
+ </row>
+ <row>
+ <entry><literal><<@ (anyelement,anyarray)</literal></entry>
+ </row>
<row>
<entry><literal>= (anyarray,anyarray)</literal></entry>
</row>
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 623962d1d8..6de6c33c75 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10;
for arrays, which supports indexed queries using these operators:
<synopsis>
-<@ @> = &&
+<@ @> <<@ @>> = &&
</synopsis>
(See <xref linkend="functions-array"/> for the meaning of
diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c
index bf73e32932..eb7d13dbba 100644
--- a/src/backend/access/gin/ginarrayproc.c
+++ b/src/backend/access/gin/ginarrayproc.c
@@ -24,6 +24,7 @@
#define GinContainsStrategy 2
#define GinContainedStrategy 3
#define GinEqualStrategy 4
+#define GinContainsElemStrategy 5
/*
@@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS)
Datum
ginqueryarrayextract(PG_FUNCTION_ARGS)
{
- /* Make copy of array input to ensure it doesn't disappear while in use */
- ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
int32 *nkeys = (int32 *) PG_GETARG_POINTER(1);
StrategyNumber strategy = PG_GETARG_UINT16(2);
@@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
/* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */
bool **nullFlags = (bool **) PG_GETARG_POINTER(5);
int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);
- int16 elmlen;
- bool elmbyval;
- char elmalign;
Datum *elems;
bool *nulls;
int nelems;
- get_typlenbyvalalign(ARR_ELEMTYPE(array),
- &elmlen, &elmbyval, &elmalign);
+ if (strategy == GinContainsElemStrategy)
+ {
+ /* single element is passed, set elems to its pointer */
+ elems = palloc(sizeof(*elems));
+ *elems = PG_GETARG_DATUM(0);
+ nulls = palloc(sizeof(*nulls));
+ *nulls = PG_ARGISNULL(0);
+ nelems = 1;
+ }
+ else
+ {
+ /* Make copy of array input to ensure it doesn't disappear while in use */
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0);
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
- deconstruct_array(array,
- ARR_ELEMTYPE(array),
- elmlen, elmbyval, elmalign,
- &elems, &nulls, &nelems);
+ get_typlenbyvalalign(ARR_ELEMTYPE(array),
+ &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(array,
+ ARR_ELEMTYPE(array),
+ elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+ }
*nkeys = nelems;
*nullFlags = nulls;
@@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS)
else
*searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;
break;
+ case GinContainsElemStrategy:
+ *searchMode = GIN_SEARCH_MODE_DEFAULT;
+ break;
default:
elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d",
strategy);
@@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* result is not lossy */
*recheck = false;
/* must have all elements in check[] true, and no nulls */
@@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS)
}
break;
case GinContainsStrategy:
+ case GinContainsElemStrategy:
/* must have all elements in check[] true, and no nulls */
res = GIN_TRUE;
for (i = 0; i < nkeys; i++)
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 17a16b4c5c..518d3aaaf9 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*
+ * array_contains_elem : checks an array for a specific element
+ * adapted from array_contain_compare() for containment of a single element
+ */
+static bool
+array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype,
+ Oid collation, void **fn_extra)
+{
+ LOCAL_FCINFO(locfcinfo, 2);
+ Oid arrtype = AARR_ELEMTYPE(array);
+ TypeCacheEntry *typentry;
+ int nelems;
+ int typlen;
+ bool typbyval;
+ char typalign;
+ int i;
+ array_iter it;
+
+ if (arrtype != elemtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot compare arrays elements with element of different type")));
+
+ /*
+ * We arrange to look up the equality function only once per series of
+ * calls, assuming the element type doesn't change underneath us. The
+ * typcache is used so that we have no memory leakage when being used as
+ * an index support function.
+ */
+ typentry = (TypeCacheEntry *) *fn_extra;
+ if (typentry == NULL ||
+ typentry->type_id != arrtype)
+ {
+ typentry = lookup_type_cache(arrtype,
+ TYPECACHE_EQ_OPR_FINFO);
+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_FUNCTION),
+ errmsg("could not identify an equality operator for type %s",
+ format_type_be(arrtype))));
+ *fn_extra = (void *) typentry;
+ }
+ typlen = typentry->typlen;
+ typbyval = typentry->typbyval;
+ typalign = typentry->typalign;
+
+ /*
+ * Apply the comparison operator for the passed element against each
+ * element in the array
+ */
+ InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2,
+ collation, NULL, NULL);
+
+ /* Loop over source data */
+ nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array));
+ array_iter_setup(&it, array);
+
+ for (i = 0; i < nelems; i++)
+ {
+ Datum elt;
+ bool isnull;
+ bool oprresult;
+
+ /* Get element, checking for NULL */
+ elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign);
+
+ /*
+ * We assume that the comparison operator is strict, so a NULL can't
+ * match anything. refer to the comment in array_contain_compare()
+ */
+ if (isnull)
+ continue;
+
+ /*
+ * Apply the operator to the element pair; treat NULL as false
+ */
+ locfcinfo->args[0].value = elt;
+ locfcinfo->args[0].isnull = false;
+ locfcinfo->args[1].value = elem;
+ locfcinfo->args[1].isnull = false;
+ locfcinfo->isnull = false;
+ oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo));
+ if (!locfcinfo->isnull && oprresult)
+ return true;
+ }
+
+ return false;
+}
+
+Datum
+arraycontainselem(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0);
+ Datum elem = PG_GETARG_DATUM(1);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 0);
+
+ PG_RETURN_BOOL(result);
+}
+
+Datum
+arrayelemcontained(PG_FUNCTION_ARGS)
+{
+ AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1);
+ Datum elem = PG_GETARG_DATUM(0);
+ Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
+ Oid collation = PG_GET_COLLATION();
+ bool result;
+
+ /*
+ * we don't need to check if the elem is null or if the elem datatype and
+ * array datatype match since this is handled within internal calls already
+ * (a property of polymorphic functions)
+ */
+
+ result = array_contains_elem(array, elem, elemtype, collation,
+ &fcinfo->flinfo->fn_extra);
+
+ /* Avoid leaking memory when handed toasted input */
+ AARR_FREE_IF_COPY(array, 1);
+
+ PG_RETURN_BOOL(result);
+}
+
/*-----------------------------------------------------------------------------
* Array iteration functions
* These functions are used to iterate efficiently through arrays
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0f7ff63669..8a14fc7140 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1242,6 +1242,9 @@
{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
amoprighttype => 'anyarray', amopstrategy => '4',
amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' },
+{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray',
+ amoprighttype => 'anyelement', amopstrategy => '5',
+ amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' },
# btree enum_ops
{ amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum',
diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat
index 85395a81ee..c6e809b88d 100644
--- a/src/include/catalog/pg_operator.dat
+++ b/src/include/catalog/pg_operator.dat
@@ -2761,7 +2761,7 @@
oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel',
oprjoin => 'positionjoinsel' },
-# overlap/contains/contained for arrays
+# overlap/contains/contained/elemcontained/containselem for arrays
{ oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps',
oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray',
oprresult => 'bool', oprcom => '&&(anyarray,anyarray)',
@@ -2778,6 +2778,18 @@
oprresult => 'bool', oprcom => '@>(anyarray,anyarray)',
oprcode => 'arraycontained', oprrest => 'arraycontsel',
oprjoin => 'arraycontjoinsel' },
+{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP',
+ descr => 'elem is contained by',
+ oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray',
+ oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)',
+ oprcode => 'arrayelemcontained', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
+{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP',
+ descr => 'contains elem',
+ oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement',
+ oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)',
+ oprcode => 'arraycontainselem', oprrest => 'arraycontsel',
+ oprjoin => 'arraycontjoinsel' },
# capturing operators to preserve pre-8.3 behavior of text concatenation
{ oid => '2779', descr => 'concatenate',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 93393fcfd4..8d82e64f86 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8200,6 +8200,12 @@
{ oid => '2749',
proname => 'arraycontained', prorettype => 'bool',
proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' },
+{ oid => '6109',
+ proname => 'arrayelemcontained', prorettype => 'bool',
+ proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' },
+{ oid => '6107',
+ proname => 'arraycontainselem', prorettype => 'bool',
+ proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' },
# BRIN minmax
{ oid => '3383', descr => 'BRIN minmax support',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index 3e3a1beaab..03ce07e219 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
(6 rows)
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+ 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
+ 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
+(6 rows)
+
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
(8 rows)
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
+ seqno | i | t
+-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
+ 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
+ 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
+ 53 | {38,17} | {AAAAAAAAAAA21658}
+ 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
+ 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
+ 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
+(8 rows)
+
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
seqno | i | t
-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------
@@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
-------+---+---
(0 rows)
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
+ seqno | i | t
+-------+---+---
+(0 rows)
+
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
seqno | i | t
-------+---+---
@@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
(4 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
+ 22 | {11,6,56,62,53,30} | {AAAAAAAA72908}
+ 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
+ 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+(4 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
seqno | i | t
-------+-----------------------+--------------------------------------------------------------------------------------------------------------------------------------------
@@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
96 | {23,97,43} | {AAAAAAAAAA646,A87088}
(3 rows)
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
+ seqno | i | t
+-------+------------------+--------------------------------------------------------------------
+ 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309}
+ 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
+ 96 | {23,97,43} | {AAAAAAAAAA646,A87088}
+(3 rows)
+
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
seqno | i | t
-------+------------------+--------------------------------------------------------------------
diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out
index 6402e89c7f..7fc7436646 100644
--- a/src/test/regress/expected/gin.out
+++ b/src/test/regress/expected/gin.out
@@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
3
(1 row)
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 1)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 1)
+(5 rows)
+
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+ QUERY PLAN
+-----------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on gin_test_tbl
+ Recheck Cond: (i @>> 999)
+ -> Bitmap Index Scan on gin_test_idx
+ Index Cond: (i @>> 999)
+(5 rows)
+
+select count(*) from gin_test_tbl where i @>> 1;
+ count
+-------
+ 2997
+(1 row)
+
+select count(*) from gin_test_tbl where i @>> 999;
+ count
+-------
+ 3
+(1 row)
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
explain (costs off)
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index 254ca06d3d..5de5ab6d13 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1173,6 +1173,7 @@ ORDER BY 1, 2;
<-> | <->
<< | >>
<<= | >>=
+ <<@ | @>>
<= | >=
<> | <>
<@ | @>
@@ -1188,7 +1189,7 @@ ORDER BY 1, 2;
~<=~ | ~>=~
~<~ | ~>~
~= | ~=
-(29 rows)
+(30 rows)
-- Likewise for negator pairs.
SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2
@@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3;
2742 | 2 | @@@
2742 | 3 | <@
2742 | 4 | =
+ 2742 | 5 | @>>
2742 | 7 | @>
2742 | 9 | ?
2742 | 10 | ?|
@@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3;
4000 | 28 | ^@
4000 | 29 | <^
4000 | 30 | >^
-(123 rows)
+(124 rows)
-- Check that all opclass search operators have selectivity estimators.
-- This is not absolutely required, but it seems a reasonable thing
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 912233ef96..944fa3afdd 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}";
SELECT ARRAY[1.1] || ARRAY[2,3,4];
SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno;
@@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno;
+SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno;
SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno;
+SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno;
diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql
index 5194afcc1f..c9b40903c6 100644
--- a/src/test/regress/sql/gin.sql
+++ b/src/test/regress/sql/gin.sql
@@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999];
select count(*) from gin_test_tbl where i @> array[1, 999];
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 1;
+explain (costs off)
+select count(*) from gin_test_tbl where i @>> 999;
+
+select count(*) from gin_test_tbl where i @>> 1;
+select count(*) from gin_test_tbl where i @>> 999;
+
-- Very weak test for gin_fuzzy_search_limit
set gin_fuzzy_search_limit = 1000;
--
2.30.1
--------------3A927D779D0A0B91F132AD46--
^ permalink raw reply [nested|flat] 52+ messages in thread
* Re: pg_plan_advice
@ 2026-01-08 16:30 Lukas Fittl <[email protected]>
2026-01-08 16:37 ` Re: pg_plan_advice Robert Haas <[email protected]>
0 siblings, 1 reply; 52+ messages in thread
From: Lukas Fittl @ 2026-01-08 16:30 UTC (permalink / raw)
To: Robert Haas <[email protected]>; +Cc: Jacob Champion <[email protected]>; Dian Fay <[email protected]>; Matheus Alcantara <[email protected]>; Jakub Wartak <[email protected]>; PostgreSQL Hackers <[email protected]>; Michael Paquier <[email protected]>
On Thu, Jan 8, 2026 at 8:07 AM Robert Haas <[email protected]> wrote:
> > The biggest change in the regression test output was due to how the "Parallel" hint worked in pg_hint_plan
> > (basically it was setting parallel_*_cost to zero, and then messed with the gucs that factor into
> > compute_parallel_worker) -- I think the only sensible thing to do is to change that in pg_hint_plan, and
> > instead rely on rejecting non-partial paths with PGS_CONSIDER_NONPARTIAL if "hard" enforcement of
> > parallelism is requested. That caused some minor plan changes, but I think they can still be argued to be
> > matching the user's intent of "make a scan involving this relation parallel".
>
> Cool. I'm sort of curious what changed, but maybe it's not important
> enough to spend time discussing right now.
In my assessment there were roughly four kinds of changes on the
regression test outputs for pg_hint_plan:
1) The order of operations (e.g. whats the inner/outer rel in a
parallel plan) - I think that's probably caused by the previous zero
cost confusing the planner
2) The placement of the Gather node in the case of joins (its now on
top of the join in more cases, vs below it) - I think that's also
caused by the previous zero cost logic
3) Parallelism wasn't enforced before, but is enforced now (e.g.
sequential scans on empty relations didn't get a Gather node
previously)
4) Worker count changed because rel_parallel_workers is still limited
by max_parallel_workers_per_gather, and so my patched version now sets
that for the whole query to the highest of the workers requested in
Parallel hints (vs before it was able to keep that to just what a plan
node needed)
> > There were two bugs in 0004 that I had to fix to make this work:
> >
> > In cost_index, we are checking "path->path.parallel_workers == 0", but parallel_workers only gets
> > set later in the function, causing the PGS_CONSIDER_NONPARTIAL mask to not be applied. Replacing
> > this with checking the "partial_path" argument instead makes it work.
>
> I agree that this is a bug. I'm thinking this might be the appropriate fix:
>
> enable_mask = (indexonly ? PGS_INDEXONLYSCAN : PGS_INDEXSCAN)
> - | (path->path.parallel_workers == 0 ? PGS_CONSIDER_NONPARTIAL : 0);
> + | (partial_path ? 0 : PGS_CONSIDER_NONPARTIAL);
Yup, that's exactly the change I had locally that worked as expected.
> > In cost_samplescan, we set the PGS_CONSIDER_NONPARTIAL mask if its a non-partial path, but that
> > causes Sample Scans to always be disabled when setting PGS_CONSIDER_NONPARTIAL on the
> > relation. I think we could simply drop that check, since we never generate partial sample scan paths.
>
> This one is less obvious to me. I mean, if PGS_CONSIDER_NONPARTIAL
> lets us consider non-partial plans, and a sample scan is a non-partial
> plan, then shouldn't the flag need to be set in order for us to
> consider it? If not, maybe we need to rethink the name or the
> semantics of that bit in some way.
Yeah, I would agree with you that is inconsistent with the flag's name
- but on the flip side, its difficult for the caller to conditionally
set the flag (which you'd have to do to avoid a "Disabled" showing in
the plan), since we're setting it on the RelOptInfo (do we know if the
scan is a sample scan at that point?).
I wonder if its worth considering to invert the flag, i.e.
"PGS_PREFER_PARTIAL" - that way its clear that in case of paths that
can't be partial, we use the non-partial version without a penalty.
PS: I realized my prior mails were not plain text and had bad word
wrap (thanks Gmail-based workflow!) - hopefully this one is better :)
Thanks,
Lukas
--
Lukas Fittl
^ permalink raw reply [nested|flat] 52+ messages in thread
* Re: pg_plan_advice
2026-01-08 16:30 Re: pg_plan_advice Lukas Fittl <[email protected]>
@ 2026-01-08 16:37 ` Robert Haas <[email protected]>
0 siblings, 0 replies; 52+ messages in thread
From: Robert Haas @ 2026-01-08 16:37 UTC (permalink / raw)
To: Lukas Fittl <[email protected]>; +Cc: Jacob Champion <[email protected]>; Dian Fay <[email protected]>; Matheus Alcantara <[email protected]>; Jakub Wartak <[email protected]>; PostgreSQL Hackers <[email protected]>; Michael Paquier <[email protected]>
On Thu, Jan 8, 2026 at 11:31 AM Lukas Fittl <[email protected]> wrote:
> Yeah, I would agree with you that is inconsistent with the flag's name
> - but on the flip side, its difficult for the caller to conditionally
> set the flag (which you'd have to do to avoid a "Disabled" showing in
> the plan), since we're setting it on the RelOptInfo (do we know if the
> scan is a sample scan at that point?).
How about checking rte->tablesample, as set_rel_pathlist does?
--
Robert Haas
EDB: http://www.enterprisedb.com
^ permalink raw reply [nested|flat] 52+ messages in thread
end of thread, other threads:[~2026-01-08 16:37 UTC | newest]
Thread overview: 52+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-04-04 15:42 Re: PATCH: Make pg_stop_backup() archive wait optional Stephen Frost <[email protected]>
2017-04-04 16:23 ` David Steele <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]>
2026-01-08 16:30 Re: pg_plan_advice Lukas Fittl <[email protected]>
2026-01-08 16:37 ` Re: pg_plan_advice Robert Haas <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox