($INBOX_DIR/description missing)  
help / color / mirror / Atom feed
[PATCH 1/3] anyarray_anyelement_operators
49+ messages / 2 participants
[nested] [flat]

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10  Mark Rofail <[email protected]>
  0 siblings, 0 replies; 49+ 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>@&gt;&gt;</literal> <type>anyelement</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Does the array contain the specified element?
+       </para>
+       <para>
+        <literal>ARRAY[1,4,3] @&gt;&gt; 3</literal>
+        <returnvalue>t</returnvalue>
+       </para></entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <type>anyelement</type> <literal>&lt;&lt;@</literal> <type>anyarray</type>
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Is the specified element contained in the array?
+       </para>
+       <para>
+        <literal>2 &lt;&lt;@ 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>&amp;&amp;</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>&amp;&amp; (anyarray,anyarray)</literal></entry>
      </row>
      <row>
@@ -93,6 +93,12 @@
      <row>
       <entry><literal>&lt;@ (anyarray,anyarray)</literal></entry>
      </row>
+     <row>
+      <entry><literal>@&gt;&gt; (anyarray,anyelement)</literal></entry>
+     </row>
+     <row>
+      <entry><literal>&lt;&lt;@ (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>
-&lt;@ &nbsp; @&gt; &nbsp; = &nbsp; &amp;&amp;
+&lt;@ &nbsp; @&gt; &nbsp; &lt;&lt;@ &nbsp; @&gt;&gt; &nbsp; = &nbsp; &amp;&amp;
 </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] 49+ messages in thread

* Re: PostgreSQL 17 Beta 1 release announcement draft
@ 2024-05-22 23:17  Jonathan S. Katz <[email protected]>
  0 siblings, 0 replies; 49+ messages in thread

From: Jonathan S. Katz @ 2024-05-22 23:17 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

On 5/19/24 5:34 PM, Jonathan S. Katz wrote:
> On 5/15/24 9:45 PM, Jonathan S. Katz wrote:
>> Hi,
>>
>> Attached is a copy of the PostgreSQL 17 Beta 1 release announcement 
>> draft. This contains a user-facing summary of some of the features 
>> that will be available in the Beta, as well as a call to test. I've 
>> made an effort to group them logically around the different workflows 
>> they affect.
>>
>> A few notes:
>>
>> * The section with the features is not 80-char delimited. I will do 
>> that before the final copy
>>
>> * There is an explicit callout that we've added in the SQL/JSON 
>> features that were previously reverted in PG15. I want to ensure we're 
>> transparent about that, but also use it as a hook to get people testing.
>>
>> When reviewing:
>>
>> * Please check for correctness of feature descriptions, keeping in 
>> mind this is targeted for a general audience
>>
>> * Please indicate if you believe there's a notable omission, or if we 
>> should omit any of these callouts
>>
>> * Please indicate if a description is confusing - I'm happy to rewrite 
>> to ensure it's clearer.
>>
>> Please provide feedback no later than Wed 2024-05-22 18:00 UTC. As the 
>> beta release takes some extra effort, I want to ensure all changes are 
>> in with time to spare before release day.
> 
> Thanks for all the feedback to date. Please see the next revision. 
> Again, please provide feedback no later than 2024-05-22 18:00 UTC.

Thanks again everyone for all your feedback. Attached is the final(-ish, 
as I'll do one more readthrough before release) draft of the release 
announcement.

If you catch something and are able to post it prior to 2024-05-23 12:00 
UTC, I may be able to incorporate into the announcement.

Thanks!

Jonathan


The PostgreSQL Global Development Group announces that the first beta release of
PostgreSQL 17 is now [available for download](https://www.postgresql.org/download/).
This release contains previews of all features that will be available when
PostgreSQL 17 is made generally available, though some details of the release
can change during the beta period.

You can find information about all of the PostgreSQL 17 features and changes in
the [release notes](https://www.postgresql.org/docs/17/release-17.html):

  [https://www.postgresql.org/docs/17/release-17.html](https://www.postgresql.org/docs/17/release-17.html)

In the spirit of the open source PostgreSQL community, we strongly encourage you
to test the new features of PostgreSQL 17 on your systems to help us eliminate
bugs or other issues that may exist. While we do not advise you to run
PostgreSQL 17 Beta 1 in production environments, we encourage you to find ways
to run your typical application workloads against this beta release.

Your testing and feedback will help the community ensure that the PostgreSQL 17
release upholds our standards of delivering a stable, reliable release of the
world's most advanced open source relational database. Please read more about
our [beta testing process](https://www.postgresql.org/developer/beta/) and how
you can contribute:

  [https://www.postgresql.org/developer/beta/](https://www.postgresql.org/developer/beta/)

PostgreSQL 17 Feature Highlights
--------------------------------

### Query and Operational Performance Improvements

PostgreSQL 17 builds on recent releases and continues to improve performance across the entire system. [Vacuum](https://www.postgresql.org/docs/17/routine-vacuuming.html), the PostgreSQL process responsible for reclaiming storage, has a new internal data structure that has shown up to a 20x memory reduction for vacuum, along with improvements in overall time to complete its work. Additionally, the vacuum process no longer has a `1GB` limit on the memory it can use (controlled by [`maintenance_work_mem`](https://www.postgresql.org/docs/17/runtime-config-resource.html#GUC-MAINTENANCE-WORK-MEM)), giving you the option to apply more resources to vacuuming.

This release introduces an interface to stream I/O, and can show performance improvements when performing sequential scans and running [`ANALYZE`](https://www.postgresql.org/docs/17/sql-analyze.html). PostgreSQL 17 also includes configuration parameters that can control scalability of [transaction, subtransaction and multixact buffers](https://www.postgresql.org/docs/17/runtime-config-resource.html#GUC-MULTIXACT-MEMBER-BUFFERS).

PostgreSQL 17 can now use both planner statistics and the sort order of [common table expressions](https://www.postgresql.org/docs/17/queries-with.html) (aka [`WITH` queries](https://www.postgresql.org/docs/17/queries-with.html)) to further optimize these queries and help them to execute more quickly. Additionally, this release significantly improves execution time of queries that use the `IN` clause with a [B-tree index](https://www.postgresql.org/docs/17/indexes-types.html#INDEXES-TYPES-BTREE). Starting with this release, PostgreSQL can remove redundant `IS NOT NULL` statements from execution on columns that have a `NOT NULL` constraint, and no longer needs to do work on queries that contain an `IS NULL` clause on an `IS NOT NULL` column. Now as of PostgreSQL 17, you can use parallel index builds for [BRIN](https://www.postgresql.org/docs/17/brin.html) indexes.

Workloads with highly concurrent changes can benefit from PostgreSQL 17 with an improvement to how [write-ahead log](https://www.postgresql.org/docs/17/wal-intro.html) ([WAL](https://www.postgresql.org/docs/17/wal-intro.html)) locks are managed, with some tests showing up to a 2x performance improvement. 

Finally, PostgreSQL 17 adds more explicit SIMD instructions, including AVX-512 support for the [`bit_count](https://www.postgresql.org/docs/17/functions-bitstring.html) function.


### Partitioned and distributed workloads enhancements

PostgreSQL 17 brings more flexibility to partition management, adding the ability to both split and merge partitions, and adds support to partitioned tables for identity columns and exclusion constraints. Additionally, the [PostgreSQL foreign data wrapper](https://www.postgresql.org/docs/17/postgres-fdw.html) ([`postgres_fdw`](https://www.postgresql.org/docs/17/postgres-fdw.html)) can realize performance benefits from queries with `EXISTS` and `IN` subqueries as these can now be pushed down to the remote server.

PostgreSQL 17 adds new capabilities to logical replication that make it simpler to use it in high availability workloads and on upgrades. Starting with upgrades from PostgreSQL 17 to newer versions, you no longer need to drop [logical replication slots](https://www.postgresql.org/docs/17/logical-replication-subscription.html#LOGICAL-REPLICATION-SUBSCRI...) when using [`pg_upgrade`](https://www.postgresql.org/docs/17/pgupgrade.html), allowing you to avoid resynchronizing data after an upgrade. Additionally, this release introduces failover control for logical replication, providing more control for managing PostgreSQL databases in high availability environments. PostgreSQL 17 also lets logical replication subscribers use `hash` indexes for lookups, and introduces the `pg_createsubscriber` command-line tool for adding logical replication on a replica using physical replication.

### Developer Experience

PostgreSQL 17 continues to build on the SQL/JSON standard, adding support for the `JSON_TABLE` features that can convert JSON to a standard PostgreSQL table, and SQL/JSON constructor (`JSON`, `JSON_SCALAR`, `JSON_SERIALIZE`) and query functions (`JSON_EXISTS`, `JSON_QUERY`, `JSON_VALUE`). Notably, these features were originally planned for the PostgreSQL 15 release but were reverted during the beta period due to design considerations, which is one reason we ask for you to help us test features during beta! Additionally, PostgreSQL 17 adds more functionality to its `jsonpath` implementation, including the ability to convert JSON values to different data types.

The [`MERGE`](https://www.postgresql.org/docs/17/sql-merge.html) command now supports the `RETURNING` clause, letting you further work with modified rows. You can also see what part of the `MERGE` command modified the row using the new `merge_action` function. PostgreSQL 17 also lets you update views using the `MERGE` command, and adds a `WHEN NOT MATCHED BY SOURCE` clause to let you provide behavior if a source row doesn't meet the conditions.

[`COPY`](https://www.postgresql.org/docs/17/sql-copy.html) is used to efficiently bulk load and export data from PostgreSQL, and now with PostgreSQL 17 you may see up to a 2x performance improvement when exporting large rows. Additionally, `COPY` has improved performance when the source encoding matches the destination encoding, and has a new option, `ON_ERROR`, that lets the copy proceed even if there is an error inserting a row. PostgreSQL 17 also provides better support for [asynchronous and more secure query cancellation routines](https://www.postgresql.org/docs/17/libpq-cancel.html), which drivers can adopt using the libpq API.

PostgreSQL 17 includes a built-in collation provider that provides similar sorting semantics to the `C` collation except with `UTF-8` encoding rather than `SQL_ASCII`. This new collation is guaranteed to be immutable, ensuring that the return values of your sorts won't change regardless of what system your PostgreSQL installation runs on.

### Security Features

PostgreSQL 17 adds a new connection parameter, `sslnegotation`, which allows PostgreSQL to perform direct TLS handshakes when using [ALPN](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation), eliminating a network roundtrip. PostgreSQL is registered as `postgresql` in the ALPN directory.

This release introduces event triggers that execute during authentication, and in libpq includes a new API called `PQchangePassword` that automatically hashes passwords on the client-side to prevent accidental plaintext logging in the server.

PostgreSQL 17 adds a new [predefined role](https://www.postgresql.org/docs/17/predefined-roles.html) called `pg_maintain` that gives users permission to execute `VACUUM`, `ANALYZE`, `CLUSTER`, `REFRESH MATERIALIZED VIEW`, `REINDEX`, and `LOCK TABLE` on all relations. This release also ensures the `search_path` is safe for maintenance operations like `VACUUM`, `ANALYZE`, `CLUSTER`, `REFRESH MATERIALIZED VIEW` and `INDEX`. Finally, users can now use `ALTER SYSTEM` to set the values for unrecognized custom server variables.

### Backup and Export Management

PostgreSQL 17 introduces the ability to perform incremental backups using [`pg_basebackup`](https://www.postgresql.org/docs/17/app-pgbasebackup.html), and adds a new utility called [`pg_combinebackup`](https://www.postgresql.org/docs/17/app-pgcombinebackup.html) that's used as part of the backup restoration process. This release adds a new flag to [`pg_dump`](https://www.postgresql.org/docs/17/app-pgdump.html) called `--filter`, letting you specify a file that contains instructions on what objects to include/exclude from a dump.

### Monitoring

The [`EXPLAIN`](https://www.postgresql.org/docs/17/sql-explain.html) command, which provides information about query plans and execution, adds two new options: `SERIALIZE`, which shows the amount of time it takes to convert data for network transmission, and `MEMORY`, which reports optimizer memory usage. Additionally, `EXPLAIN` can now show how much time is spent for I/O block reads and writes.

PostgreSQL 17 normalizes the parameters for `CALL` in [`pg_stat_statements`](https://www.postgresql.org/docs/17/pgstatstatements.html), reducing the number of entries for frequently called stored procedures. Additionally, [`VACUUM` progress reporting](https://www.postgresql.org/docs/devel/progress-reporting.html#VACUUM-PROGRESS-REPORTING) now shows the progress of vacuuming indexes. PostgreSQL 17 also introduces a new view, [`pg_wait_events`](https://www.postgresql.org/docs/17/view-pg-wait-events.html), which provides descriptions about wait events and can be combined with `pg_stat_activity` to give more insight into why an active session is waiting. Additionally, some information in the [`pg_stat_bgwriter`](https://www.postgresql.org/docs/17/monitoring-stats.html#MONITORING-PG-STAT-BGWRITER-VIEW) view is now split out into the new [`pg_stat_checkpointer`](https://www.postgresql.org/docs/17/monitoring-stats.html#MONITORING-PG-STAT-CHECKPOINTER-VIEW) view.

Additional Features
-------------------

Many other new features and improvements have been added to PostgreSQL 17. Many
of these may also be helpful for your use cases. Please see the
[release notes](https://www.postgresql.org/docs/17/release-17.html) for a
complete list of new and changed features:

  [https://www.postgresql.org/docs/17/release-17.html](https://www.postgresql.org/docs/17/release-17.html)

Testing for Bugs & Compatibility
--------------------------------

The stability of each PostgreSQL release greatly depends on you, the community,
to test the upcoming version with your workloads and testing tools in order to
find bugs and regressions before the general availability of PostgreSQL 17. As
this is a Beta, minor changes to database behaviors, feature details, and APIs
are still possible. Your feedback and testing will help determine the final
tweaks on the new features, so please test in the near future. The quality of
user testing helps determine when we can make a final release.

A list of [open issues](https://wiki.postgresql.org/wiki/PostgreSQL_17_Open_Items)
is publicly available in the PostgreSQL wiki.  You can
[report bugs](https://www.postgresql.org/account/submitbug/) using this form on
the PostgreSQL website:

  [https://www.postgresql.org/account/submitbug/](https://www.postgresql.org/account/submitbug/)

Beta Schedule
-------------

This is the first beta release of version 17. The PostgreSQL Project will
release additional betas as required for testing, followed by one or more
release candidates, until the final release around September/October 2024. For further
information please see the [Beta Testing](https://www.postgresql.org/developer/beta/)
page.

Links
-----

* [Download](https://www.postgresql.org/download/)
* [Beta Testing Information](https://www.postgresql.org/developer/beta/)
* [PostgreSQL 17 Beta Release Notes](https://www.postgresql.org/docs/17/release-17.html)
* [PostgreSQL 17 Open Issues](https://wiki.postgresql.org/wiki/PostgreSQL_17_Open_Items)
* [Feature Matrix](https://www.postgresql.org/about/featurematrix/)
* [Submit a Bug](https://www.postgresql.org/account/submitbug/)
* [Follow @postgresql on X/Twitter](https://twitter.com/postgresql)
* [Donate](https://www.postgresql.org/about/donate/)


Attachments:

  [text/plain] 17beta1.md (12.6K, ../../[email protected]/2-17beta1.md)
  download | inline:
The PostgreSQL Global Development Group announces that the first beta release of
PostgreSQL 17 is now [available for download](https://www.postgresql.org/download/).
This release contains previews of all features that will be available when
PostgreSQL 17 is made generally available, though some details of the release
can change during the beta period.

You can find information about all of the PostgreSQL 17 features and changes in
the [release notes](https://www.postgresql.org/docs/17/release-17.html):

  [https://www.postgresql.org/docs/17/release-17.html](https://www.postgresql.org/docs/17/release-17.html)

In the spirit of the open source PostgreSQL community, we strongly encourage you
to test the new features of PostgreSQL 17 on your systems to help us eliminate
bugs or other issues that may exist. While we do not advise you to run
PostgreSQL 17 Beta 1 in production environments, we encourage you to find ways
to run your typical application workloads against this beta release.

Your testing and feedback will help the community ensure that the PostgreSQL 17
release upholds our standards of delivering a stable, reliable release of the
world's most advanced open source relational database. Please read more about
our [beta testing process](https://www.postgresql.org/developer/beta/) and how
you can contribute:

  [https://www.postgresql.org/developer/beta/](https://www.postgresql.org/developer/beta/)

PostgreSQL 17 Feature Highlights
--------------------------------

### Query and Operational Performance Improvements

PostgreSQL 17 builds on recent releases and continues to improve performance across the entire system. [Vacuum](https://www.postgresql.org/docs/17/routine-vacuuming.html), the PostgreSQL process responsible for reclaiming storage, has a new internal data structure that has shown up to a 20x memory reduction for vacuum, along with improvements in overall time to complete its work. Additionally, the vacuum process no longer has a `1GB` limit on the memory it can use (controlled by [`maintenance_work_mem`](https://www.postgresql.org/docs/17/runtime-config-resource.html#GUC-MAINTENANCE-WORK-MEM)), giving you the option to apply more resources to vacuuming.

This release introduces an interface to stream I/O, and can show performance improvements when performing sequential scans and running [`ANALYZE`](https://www.postgresql.org/docs/17/sql-analyze.html). PostgreSQL 17 also includes configuration parameters that can control scalability of [transaction, subtransaction and multixact buffers](https://www.postgresql.org/docs/17/runtime-config-resource.html#GUC-MULTIXACT-MEMBER-BUFFERS).

PostgreSQL 17 can now use both planner statistics and the sort order of [common table expressions](https://www.postgresql.org/docs/17/queries-with.html) (aka [`WITH` queries](https://www.postgresql.org/docs/17/queries-with.html)) to further optimize these queries and help them to execute more quickly. Additionally, this release significantly improves execution time of queries that use the `IN` clause with a [B-tree index](https://www.postgresql.org/docs/17/indexes-types.html#INDEXES-TYPES-BTREE). Starting with this release, PostgreSQL can remove redundant `IS NOT NULL` statements from execution on columns that have a `NOT NULL` constraint, and no longer needs to do work on queries that contain an `IS NULL` clause on an `IS NOT NULL` column. Now as of PostgreSQL 17, you can use parallel index builds for [BRIN](https://www.postgresql.org/docs/17/brin.html) indexes.

Workloads with highly concurrent changes can benefit from PostgreSQL 17 with an improvement to how [write-ahead log](https://www.postgresql.org/docs/17/wal-intro.html) ([WAL](https://www.postgresql.org/docs/17/wal-intro.html)) locks are managed, with some tests showing up to a 2x performance improvement. 

Finally, PostgreSQL 17 adds more explicit SIMD instructions, including AVX-512 support for the [`bit_count](https://www.postgresql.org/docs/17/functions-bitstring.html) function.


### Partitioned and distributed workloads enhancements

PostgreSQL 17 brings more flexibility to partition management, adding the ability to both split and merge partitions, and adds support to partitioned tables for identity columns and exclusion constraints. Additionally, the [PostgreSQL foreign data wrapper](https://www.postgresql.org/docs/17/postgres-fdw.html) ([`postgres_fdw`](https://www.postgresql.org/docs/17/postgres-fdw.html)) can realize performance benefits from queries with `EXISTS` and `IN` subqueries as these can now be pushed down to the remote server.

PostgreSQL 17 adds new capabilities to logical replication that make it simpler to use it in high availability workloads and on upgrades. Starting with upgrades from PostgreSQL 17 to newer versions, you no longer need to drop [logical replication slots](https://www.postgresql.org/docs/17/logical-replication-subscription.html#LOGICAL-REPLICATION-SUBSCRIPTION-SLOT) when using [`pg_upgrade`](https://www.postgresql.org/docs/17/pgupgrade.html), allowing you to avoid resynchronizing data after an upgrade. Additionally, this release introduces failover control for logical replication, providing more control for managing PostgreSQL databases in high availability environments. PostgreSQL 17 also lets logical replication subscribers use `hash` indexes for lookups, and introduces the `pg_createsubscriber` command-line tool for adding logical replication on a replica using physical replication.

### Developer Experience

PostgreSQL 17 continues to build on the SQL/JSON standard, adding support for the `JSON_TABLE` features that can convert JSON to a standard PostgreSQL table, and SQL/JSON constructor (`JSON`, `JSON_SCALAR`, `JSON_SERIALIZE`) and query functions (`JSON_EXISTS`, `JSON_QUERY`, `JSON_VALUE`). Notably, these features were originally planned for the PostgreSQL 15 release but were reverted during the beta period due to design considerations, which is one reason we ask for you to help us test features during beta! Additionally, PostgreSQL 17 adds more functionality to its `jsonpath` implementation, including the ability to convert JSON values to different data types.

The [`MERGE`](https://www.postgresql.org/docs/17/sql-merge.html) command now supports the `RETURNING` clause, letting you further work with modified rows. You can also see what part of the `MERGE` command modified the row using the new `merge_action` function. PostgreSQL 17 also lets you update views using the `MERGE` command, and adds a `WHEN NOT MATCHED BY SOURCE` clause to let you provide behavior if a source row doesn't meet the conditions.

[`COPY`](https://www.postgresql.org/docs/17/sql-copy.html) is used to efficiently bulk load and export data from PostgreSQL, and now with PostgreSQL 17 you may see up to a 2x performance improvement when exporting large rows. Additionally, `COPY` has improved performance when the source encoding matches the destination encoding, and has a new option, `ON_ERROR`, that lets the copy proceed even if there is an error inserting a row. PostgreSQL 17 also provides better support for [asynchronous and more secure query cancellation routines](https://www.postgresql.org/docs/17/libpq-cancel.html), which drivers can adopt using the libpq API.

PostgreSQL 17 includes a built-in collation provider that provides similar sorting semantics to the `C` collation except with `UTF-8` encoding rather than `SQL_ASCII`. This new collation is guaranteed to be immutable, ensuring that the return values of your sorts won't change regardless of what system your PostgreSQL installation runs on.

### Security Features

PostgreSQL 17 adds a new connection parameter, `sslnegotation`, which allows PostgreSQL to perform direct TLS handshakes when using [ALPN](https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation), eliminating a network roundtrip. PostgreSQL is registered as `postgresql` in the ALPN directory.

This release introduces event triggers that execute during authentication, and in libpq includes a new API called `PQchangePassword` that automatically hashes passwords on the client-side to prevent accidental plaintext logging in the server.

PostgreSQL 17 adds a new [predefined role](https://www.postgresql.org/docs/17/predefined-roles.html) called `pg_maintain` that gives users permission to execute `VACUUM`, `ANALYZE`, `CLUSTER`, `REFRESH MATERIALIZED VIEW`, `REINDEX`, and `LOCK TABLE` on all relations. This release also ensures the `search_path` is safe for maintenance operations like `VACUUM`, `ANALYZE`, `CLUSTER`, `REFRESH MATERIALIZED VIEW` and `INDEX`. Finally, users can now use `ALTER SYSTEM` to set the values for unrecognized custom server variables.

### Backup and Export Management

PostgreSQL 17 introduces the ability to perform incremental backups using [`pg_basebackup`](https://www.postgresql.org/docs/17/app-pgbasebackup.html), and adds a new utility called [`pg_combinebackup`](https://www.postgresql.org/docs/17/app-pgcombinebackup.html) that's used as part of the backup restoration process. This release adds a new flag to [`pg_dump`](https://www.postgresql.org/docs/17/app-pgdump.html) called `--filter`, letting you specify a file that contains instructions on what objects to include/exclude from a dump.

### Monitoring

The [`EXPLAIN`](https://www.postgresql.org/docs/17/sql-explain.html) command, which provides information about query plans and execution, adds two new options: `SERIALIZE`, which shows the amount of time it takes to convert data for network transmission, and `MEMORY`, which reports optimizer memory usage. Additionally, `EXPLAIN` can now show how much time is spent for I/O block reads and writes.

PostgreSQL 17 normalizes the parameters for `CALL` in [`pg_stat_statements`](https://www.postgresql.org/docs/17/pgstatstatements.html), reducing the number of entries for frequently called stored procedures. Additionally, [`VACUUM` progress reporting](https://www.postgresql.org/docs/devel/progress-reporting.html#VACUUM-PROGRESS-REPORTING) now shows the progress of vacuuming indexes. PostgreSQL 17 also introduces a new view, [`pg_wait_events`](https://www.postgresql.org/docs/17/view-pg-wait-events.html), which provides descriptions about wait events and can be combined with `pg_stat_activity` to give more insight into why an active session is waiting. Additionally, some information in the [`pg_stat_bgwriter`](https://www.postgresql.org/docs/17/monitoring-stats.html#MONITORING-PG-STAT-BGWRITER-VIEW) view is now split out into the new [`pg_stat_checkpointer`](https://www.postgresql.org/docs/17/monitoring-stats.html#MONITORING-PG-STAT-CHECKPOINTER-VIEW) view.

Additional Features
-------------------

Many other new features and improvements have been added to PostgreSQL 17. Many
of these may also be helpful for your use cases. Please see the
[release notes](https://www.postgresql.org/docs/17/release-17.html) for a
complete list of new and changed features:

  [https://www.postgresql.org/docs/17/release-17.html](https://www.postgresql.org/docs/17/release-17.html)

Testing for Bugs & Compatibility
--------------------------------

The stability of each PostgreSQL release greatly depends on you, the community,
to test the upcoming version with your workloads and testing tools in order to
find bugs and regressions before the general availability of PostgreSQL 17. As
this is a Beta, minor changes to database behaviors, feature details, and APIs
are still possible. Your feedback and testing will help determine the final
tweaks on the new features, so please test in the near future. The quality of
user testing helps determine when we can make a final release.

A list of [open issues](https://wiki.postgresql.org/wiki/PostgreSQL_17_Open_Items)
is publicly available in the PostgreSQL wiki.  You can
[report bugs](https://www.postgresql.org/account/submitbug/) using this form on
the PostgreSQL website:

  [https://www.postgresql.org/account/submitbug/](https://www.postgresql.org/account/submitbug/)

Beta Schedule
-------------

This is the first beta release of version 17. The PostgreSQL Project will
release additional betas as required for testing, followed by one or more
release candidates, until the final release around September/October 2024. For further
information please see the [Beta Testing](https://www.postgresql.org/developer/beta/)
page.

Links
-----

* [Download](https://www.postgresql.org/download/)
* [Beta Testing Information](https://www.postgresql.org/developer/beta/)
* [PostgreSQL 17 Beta Release Notes](https://www.postgresql.org/docs/17/release-17.html)
* [PostgreSQL 17 Open Issues](https://wiki.postgresql.org/wiki/PostgreSQL_17_Open_Items)
* [Feature Matrix](https://www.postgresql.org/about/featurematrix/)
* [Submit a Bug](https://www.postgresql.org/account/submitbug/)
* [Follow @postgresql on X/Twitter](https://twitter.com/postgresql)
* [Donate](https://www.postgresql.org/about/donate/)

  [application/pgp-signature] OpenPGP_signature.asc (840B, ../../[email protected]/3-OpenPGP_signature.asc)
  download

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


end of thread, other threads:[~2024-05-22 23:17 UTC | newest]

Thread overview: 49+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
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]>
2024-05-22 23:17 Re: PostgreSQL 17 Beta 1 release announcement draft Jonathan S. Katz <[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