agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 1/3] anyarray_anyelement_operators
68+ messages / 4 participants
[nested] [flat]

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH 1/3] anyarray_anyelement_operators
@ 2021-03-13 10:01 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* [PATCH v10 1/2] anyarray_anyelement_operators
@ 2021-03-15 15:10 Mark Rofail <[email protected]>
  0 siblings, 0 replies; 68+ 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] 68+ messages in thread

* Re: per backend I/O statistics
@ 2024-12-12 04:52 Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Michael Paquier @ 2024-12-12 04:52 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Mon, Nov 25, 2024 at 03:47:59PM +0000, Bertrand Drouvot wrote:
> === Remarks
> 
> R1: as compared to v5, v6 removes the per-backend I/O stats reset from 
> pg_stat_reset_shared(). I think it makes more sense that way, since we are
> adding pg_stat_reset_single_backend_io_counters(). The per-backend I/O stats
> behaves then as the subscription stats as far the reset is concerned.

Makes sense to me to not include that in pg_stat_reset_shared().

> R2: as we can't merge the flush cb anymore, only the patches related to
> the stats_fetch_consistency/'snapshot' are missing in v6 (as compared to v5).
> I propose to re-submit them, re-start the discussion once 0001 goes in.

Yeah, thanks.  I should think more about this part, but I'm still kind
of unconvinced.  Let's do things step by step.  For now, I have looked
at v6.

+        view. The function does not return I/O statistics for the checkpointer,
+        the background writer, the startup process and the autovacuum launcher
+        as they are already visible in the <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
+        view and there is only one of those.

This last sentence seems unnecessary?  The function is named
"backend", and well, all these processes are not backends.

+    /*
+     * Maybe an auxiliary process? That should not be possible, due to
+     * pgstat_tracks_per_backend_bktype() though.
+     */
+    if (proc == NULL)
+        proc = AuxiliaryPidGetProc(backend_pid);
[...]
+        /*
+         * Maybe an auxiliary process? That should not be possible, due to
+         * pgstat_tracks_per_backend_bktype() though.
+         */
+        if (proc == NULL)
+            proc = AuxiliaryPidGetProc(pid);

This does not seem right.  Shouldn't we return immediately if
BackendPidGetProc() finds nothing matching with the PID?

+	/* Look for the backend type */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* looking for specific PID, ignore all the others */
+		if (beentry->st_procpid != pid)
+			continue;
+
+		bktype = beentry->st_backendType;
+		break;
+	}

Sounds to me that the backend type is not strictly required in this
function call if pg_stat_activity can tell already that?

+       (void) pgstat_per_backend_flush_cb(entry_ref, nowait);

I'd recommend to not directly call the callback, use a wrapper
function instead if need be.

 pgstat_count_io_op_time(IOObject io_object, IOContext io_context, IOOp io_op,
 						instr_time start_time, uint32 cnt)
 {
+
 	if (track_io_timing)

Noise diff.

 /*
- * Simpler wrapper of pgstat_io_flush_cb()
+ * Simpler wrapper of pgstat_io_flush_cb() and pgstat_per_backend_flush_cb().
  */
 void
 pgstat_flush_io(bool nowait)

This is also called in the checkpointer and the bgwriter and the
walwriter via pgstat_report_wal(), which is kind of useless.  Perhaps
just use a different, separate function instead and use that where it
makes sense (per se also the argument of upthread that backend stats
may not be only IO-related..).

Sounds to me that PgStat_BackendPendingIO should be
PgStat_BackendPendingStats?

+{ oid => '8806', descr => 'statistics: per backend IO statistics',
+  proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't',

Similarly, s/pg_stat_get_backend_io/pg_stat_get_backend_stats/?

+  descr => 'statistics: reset collected IO statistics for a single backend',
+  proname => 'pg_stat_reset_single_backend_io_counters', provolatile => 'v', 

And here, pg_stat_reset_backend_stats?

 #define PGSTAT_KIND_SUBSCRIPTION   5   /* per-subscription statistics */
+#define PGSTAT_KIND_PER_BACKEND    6 

Missing one comment here.

FWIW, I'm so-so about the addition of pg_my_stat_io, knowing that
pg_stat_get_backend_io(NULL/pg_backend_pid()) does the same job.  I
would just add a note in the docs with a query showing how to use it
with pg_stat_activity.  An example with LATERAL, doing the same work:
select a.pid, s.* from pg_stat_activity as a,
   lateral pg_stat_get_backend_io(a.pid) as s
  where pid = pg_backend_pid();
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-12 14:02 ` Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-12 14:02 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Thu, Dec 12, 2024 at 01:52:03PM +0900, Michael Paquier wrote:
> On Mon, Nov 25, 2024 at 03:47:59PM +0000, Bertrand Drouvot wrote:

> +        view. The function does not return I/O statistics for the checkpointer,
> +        the background writer, the startup process and the autovacuum launcher
> +        as they are already visible in the <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
> +        view and there is only one of those.
> 
> This last sentence seems unnecessary?  The function is named
> "backend", and well, all these processes are not backends.

Yeah, but you can find their pid through pg_stat_activity for which the pid field
description is "Process ID of this backend". Also we can find "backend_type" in
both pg_stat_activity and pg_stat_io, so I think this last sentence could help 
to avoid any confusion though.

> +    /*
> +     * Maybe an auxiliary process? That should not be possible, due to
> +     * pgstat_tracks_per_backend_bktype() though.
> +     */
> +    if (proc == NULL)
> +        proc = AuxiliaryPidGetProc(backend_pid);
> [...]
> +        /*
> +         * Maybe an auxiliary process? That should not be possible, due to
> +         * pgstat_tracks_per_backend_bktype() though.
> +         */
> +        if (proc == NULL)
> +            proc = AuxiliaryPidGetProc(pid);
> 
> This does not seem right.  Shouldn't we return immediately if
> BackendPidGetProc() finds nothing matching with the PID?

Yeah, that would work. I was keeping the AuxiliaryPidGetProc() calls just in case
we want to add the Aux processes back in the future. Replaced with a comment
in v7 attached instead.

> 
> +	/* Look for the backend type */
> +	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
> +	{
> +		LocalPgBackendStatus *local_beentry;
> +		PgBackendStatus *beentry;
> +
> +		/* Get the next one in the list */
> +		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
> +		beentry = &local_beentry->backendStatus;
> +
> +		/* looking for specific PID, ignore all the others */
> +		if (beentry->st_procpid != pid)
> +			continue;
> +
> +		bktype = beentry->st_backendType;
> +		break;
> +	}
> 
> Sounds to me that the backend type is not strictly required in this
> function call if pg_stat_activity can tell already that?

Yeah, but it's needed in pg_stat_get_backend_io() for the stats filtering (to
display only those linked to this backend type), later in the function here:

+    /*
+     * Some combinations of BackendType, IOObject, and IOContext are
+     * not valid for any type of IOOp. In such cases, omit the entire
+     * row from the view.
+     */
+    if (!pgstat_tracks_io_object(bktype, io_obj, io_context))
+          continue;

OTOH, now that we get rid of the AuxiliaryPidGetProc() call in
pg_stat_reset_single_backend_io_counters() we can also remove the need to
look for the backend type in this function.

> 
> +       (void) pgstat_per_backend_flush_cb(entry_ref, nowait);
> 
> I'd recommend to not directly call the callback, use a wrapper
> function instead if need be.

Makes sense, created its own callback in pgstat_backend.c. Bonus point, it avoids
unnecessary pgstat_tracks_per_backend_bktype() checks in :

pgstat_report_bgwriter()
pgstat_report_checkpointer()
pgstat_report_wal()

as there is no attempt to call the callback anymore in those places.

>  /*
> - * Simpler wrapper of pgstat_io_flush_cb()
> + * Simpler wrapper of pgstat_io_flush_cb() and pgstat_per_backend_flush_cb().
>   */
>  void
>  pgstat_flush_io(bool nowait)
> 
> This is also called in the checkpointer and the bgwriter and the
> walwriter via pgstat_report_wal(), which is kind of useless.  Perhaps
> just use a different, separate function instead and use that where it
> makes sense (per se also the argument of upthread that backend stats
> may not be only IO-related..).

Yeah, done that way. 

BTW, not related to this particular patch but I realized that pgstat_flush_io()
is called for the walwriter. Indeed, it's coming from the pgstat_report_wal()
call in WalWriterMain(). That can not report any I/O stats activity (as the
walwriter is not part of the I/O stats tracking, see pgstat_tracks_io_bktype()).

So it looks like, we could move pgstat_flush_io() outside of pgstat_report_wal()
and add the pgstat_flush_io() calls only where they need to be made (and so, not
in WalWriterMain()).

Maybe a dedicated thread is worth it for that, thoughts?

> Sounds to me that PgStat_BackendPendingIO should be
> PgStat_BackendPendingStats?

Yeah, can do that as that's what is being used for the pending_size for
example.

OTOH, it's clear that this one in pgstat_io.c has to be "linked" to an "IO" 
related one:

"
typedef PgStat_BackendPendingStats PgStat_PendingIO;
"

The right way would probably be to do something like:

"
typedef struct PgStat_BackendPendingStats {
    PgStat_BackendPendingIO pendingio;  
} PgStat_BackendPendingStats;
"

But I'm not sure that's worth it until we don't have a need to add more
pending stats per-backend, thoughts?

> +{ oid => '8806', descr => 'statistics: per backend IO statistics',
> +  proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't',
> 
> Similarly, s/pg_stat_get_backend_io/pg_stat_get_backend_stats/?

I think that's fine to keep pg_stat_get_backend_io(). If we add more per-backend
stats in the future then we could add "dedicated" get functions too and a generic
one retrieving all of them.

> +  descr => 'statistics: reset collected IO statistics for a single backend',
> +  proname => 'pg_stat_reset_single_backend_io_counters', provolatile => 'v', 
> 
> And here, pg_stat_reset_backend_stats?

Same as above, we could imagine that in the future the backend would get mutiple
stats and that one would want to reset only the I/O ones for example.

>  #define PGSTAT_KIND_SUBSCRIPTION   5   /* per-subscription statistics */
> +#define PGSTAT_KIND_PER_BACKEND    6 
> 
> Missing one comment here.

Yeap.

> FWIW, I'm so-so about the addition of pg_my_stat_io, knowing that
> pg_stat_get_backend_io(NULL/pg_backend_pid()) does the same job.

Okay, I don't have a strong opinion about that. Removed in v7.

> I
> would just add a note in the docs with a query showing how to use it
> with pg_stat_activity.  An example with LATERAL, doing the same work:
> select a.pid, s.* from pg_stat_activity as a,
>    lateral pg_stat_get_backend_io(a.pid) as s
>   where pid = pg_backend_pid();

I'm not sure it's worth it. I think that's clear that to get our own stats
then we need to provide our own backend pid. For example pg_stat_get_activity()
does not provide such an example using pg_stat_activity or using something like 
pg_stat_get_activity(pg_backend_pid()).

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com


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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-13 02:02   ` Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Michael Paquier @ 2024-12-13 02:02 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Thu, Dec 12, 2024 at 02:02:38PM +0000, Bertrand Drouvot wrote:
> On Thu, Dec 12, 2024 at 01:52:03PM +0900, Michael Paquier wrote:
> Yeah, but it's needed in pg_stat_get_backend_io() for the stats filtering (to
> display only those linked to this backend type), later in the function here:
> 
> +    /*
> +     * Some combinations of BackendType, IOObject, and IOContext are
> +     * not valid for any type of IOOp. In such cases, omit the entire
> +     * row from the view.
> +     */
> +    if (!pgstat_tracks_io_object(bktype, io_obj, io_context))
> +          continue;
> 
> OTOH, now that we get rid of the AuxiliaryPidGetProc() call in
> pg_stat_reset_single_backend_io_counters() we can also remove the need to
> look for the backend type in this function.

Ah, you need that for the pgstat_tracks_io_object() filtering.  I'm
wondering if we should think about making that cheaper at some point.
As a O(N^2) when coupling a call of pg_stat_get_backend_io() with a
scan of pg_stat_activity, perhaps it does not matter much anyway..

Anyway, isn't it possible that this lookup loop finishes by finding
nothing depending on concurrent updates of other beentries?  It sounds
to me that this warrants an early exit in the function.

> BTW, not related to this particular patch but I realized that pgstat_flush_io()
> is called for the walwriter. Indeed, it's coming from the pgstat_report_wal()
> call in WalWriterMain(). That can not report any I/O stats activity (as the
> walwriter is not part of the I/O stats tracking, see pgstat_tracks_io_bktype()).
>
> So it looks like, we could move pgstat_flush_io() outside of pgstat_report_wal()
> and add the pgstat_flush_io() calls only where they need to be made (and so, not
> in WalWriterMain()).

Perhaps, yes.  pgstat_tracks_io_bktype() has always been discarded
walwriters since pgstat_io.c exists.

> But I'm not sure that's worth it until we don't have a need to add more
> pending stats per-backend, thoughts?

Okay with your argument here.

>> +{ oid => '8806', descr => 'statistics: per backend IO statistics',
>> +  proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't',
>> 
>> Similarly, s/pg_stat_get_backend_io/pg_stat_get_backend_stats/?
> 
> I think that's fine to keep pg_stat_get_backend_io(). If we add more per-backend
> stats in the future then we could add "dedicated" get functions too and a generic
> one retrieving all of them.

Okay about this layer, discarding my remark.  You have a point about
that: other stats associated to a single backend may be OK if not
returned as a SRF, and they'll most likely return a different set of
attributes.

>> +  descr => 'statistics: reset collected IO statistics for a single backend',
>> +  proname => 'pg_stat_reset_single_backend_io_counters', provolatile => 'v', 
>> 
>> And here, pg_stat_reset_backend_stats?
> 
> Same as above, we could imagine that in the future the backend would get mutiple
> stats and that one would want to reset only the I/O ones for example.

Disagreed about this part.  It is slightly simpler to do a full reset
of the stats in a single entry.  If another subset of stats is added
to the backend-level entries, we could always introduce a new function
that has more control over what subset of a single backend entry is
reset.  And I'm pretty sure that we are going to need the function
that does the full reset anyway.

>> I
>> would just add a note in the docs with a query showing how to use it
>> with pg_stat_activity.  An example with LATERAL, doing the same work:
>> select a.pid, s.* from pg_stat_activity as a,
>>    lateral pg_stat_get_backend_io(a.pid) as s
>>   where pid = pg_backend_pid();
> 
> I'm not sure it's worth it. I think that's clear that to get our own stats
> then we need to provide our own backend pid. For example pg_stat_get_activity()
> does not provide such an example using pg_stat_activity or using something like 
> pg_stat_get_activity(pg_backend_pid()).

Okay.

As far as I can see, the patch relies entirely on write_to_file to
prevent any entries to be flushed out.  It means that we leave in the
dshash entries that may sit idle for as long as the server is up once
a pgproc slot is used at least once.  This scales depending on
max_connections.  It also means that we skip the sanity check about
dropped entries at shutdown, which may be a good thing to do because
we don't need to loop through them when writing the stats file.  Hmm.
Could it be better to be more aggressive with the handling of these
stats, marking them as dropped when their backend exists and cleanup
the dshash, without relying on the write flag to make sure that all
the entries are discarded at shutdown?  The point is that we do
shutdown in a controlled manner, with all backends exiting before the
checkpointer writes the stats file after the shutdown checkpoint is
completed.  The patch handles things so as entries are reset when a
procnum is reused, leaving past stats around until that happens.  We
should perhaps aim for more consistency with the way beentry is
refreshed and be more proactive with the backend entry drop or reset
at backend shutdown (pgstat_beshutdown_hook?), so as what is in the
dshash reflects exactly what's in shared memory for each PGPROC and
beentry.

Not sure that the "_per_" added in the various references of the patch
are good to keep, like pgstat_tracks_per_backend_bktype.  These could
be removed, I guess, doing also a PGSTAT_KIND_PER_BACKEND =>
PGSTAT_KIND_BACKEND?
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-13 09:20     ` Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-13 09:20 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Fri, Dec 13, 2024 at 11:02:53AM +0900, Michael Paquier wrote:
> On Thu, Dec 12, 2024 at 02:02:38PM +0000, Bertrand Drouvot wrote:
> 
> Anyway, isn't it possible that this lookup loop finishes by finding
> nothing depending on concurrent updates of other beentries?  It sounds
> to me that this warrants an early exit in the function.

Right, done that way in the attached.

> Perhaps, yes.  pgstat_tracks_io_bktype() has always been discarded
> walwriters since pgstat_io.c exists.

Yeap. The comment on top of pgstat_tracks_io_bktype() says that it's not done
"for now". I think that we could update the code as proposed until it's done.

> >> +  descr => 'statistics: reset collected IO statistics for a single backend',
> >> +  proname => 'pg_stat_reset_single_backend_io_counters', provolatile => 'v', 
> >> 
> >> And here, pg_stat_reset_backend_stats?
> > 
> > Same as above, we could imagine that in the future the backend would get mutiple
> > stats and that one would want to reset only the I/O ones for example.
> 
> Disagreed about this part.  It is slightly simpler to do a full reset
> of the stats in a single entry.  If another subset of stats is added
> to the backend-level entries, we could always introduce a new function
> that has more control over what subset of a single backend entry is
> reset.  And I'm pretty sure that we are going to need the function
> that does the full reset anyway.

Yeah, would have added it when a new stats subset would be added. It's fine
by me to have it now though, so done that way.

> As far as I can see, the patch relies entirely on write_to_file to
> prevent any entries to be flushed out.

Yes.

> It means that we leave in the
> dshash entries that may sit idle for as long as the server is up once
> a pgproc slot is used at least once.  This scales depending on
> max_connections.  It also means that we skip the sanity check about
> dropped entries at shutdown, which may be a good thing to do because
> we don't need to loop through them when writing the stats file.

Agree.

> Hmm.
> Could it be better to be more aggressive with the handling of these
> stats, marking them as dropped when their backend exists and cleanup
> the dshash, without relying on the write flag to make sure that all
> the entries are discarded at shutdown?

Yeah we can do it to be consistent with other stats kind, done.

> The point is that we do
> shutdown in a controlled manner, with all backends exiting before the
> checkpointer writes the stats file after the shutdown checkpoint is
> completed.  The patch handles things so as entries are reset when a
> procnum is reused, leaving past stats around until that happens.  We
> should perhaps aim for more consistency with the way beentry is
> refreshed and be more proactive with the backend entry drop or reset
> at backend shutdown (pgstat_beshutdown_hook?), so as what is in the
> dshash reflects exactly what's in shared memory for each PGPROC and
> beentry.

That can't be done in pgstat_beshutdown_hook because pgstat_shutdown_hook is called
before and so resets the pgStatLocal.shared_hash during pgstat_detach_shmem().

So, did it in pgstat_shutdown_hook instead. 

> Not sure that the "_per_" added in the various references of the patch
> are good to keep, like pgstat_tracks_per_backend_bktype.  These could
> be removed, I guess, doing also a PGSTAT_KIND_PER_BACKEND =>
> PGSTAT_KIND_BACKEND?

Yeah makes sense, that's consistent with other kinds: done.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com


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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-16 08:07       ` Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Michael Paquier @ 2024-12-16 08:07 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Fri, Dec 13, 2024 at 09:20:13AM +0000, Bertrand Drouvot wrote:
> Yeah makes sense, that's consistent with other kinds: done.

This looks to be taking shape.  I don't have much more comments.

Not feeling so sure about the value brought by the backend_type
returned in pg_stat_get_backend_io(), but well..

+	/* drop the backend stats entry */
+	pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber);

Oh, I've missed something.  Shouldn't pgstat_request_entry_refs_gc()
be called when this returns false?

The creation of the dshash entry is a bit too early, I think..  How
about delaying it more so as we don't create entries that could be
useless if we fail the last steps of authentication?  One spot would
be to delay the creation of the new entry at the end of
pgstat_bestart(), where we know that we are done with authentication
and that the backend is ready to report back to the client connected
to it.  It is true that some subsystems could produce stats as of the 
early transactions they generate, which is why pgstat_initialize() is
done that early in BaseInit(), but that's not really relevant for this
case?

I'm still feeling a bit uneasy about the drop done in
pgstat_shutdown_hook(); it would be nice to make sure that this
happens in a path that would run just after we're done with the
creation of the entry to limit windows where we have an entry but no
way to drop it, or vice-versa, so as the shutdown assertions would
never trigger.  Perhaps there's an argument for an entirely separate
callback that would run before pgstat is plugged off, like a new
before_shmem_exit() callback registered after the entry is created?
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-16 15:42         ` Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-16 15:42 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Mon, Dec 16, 2024 at 05:07:52PM +0900, Michael Paquier wrote:
> On Fri, Dec 13, 2024 at 09:20:13AM +0000, Bertrand Drouvot wrote:
> 
> Not feeling so sure about the value brought by the backend_type
> returned in pg_stat_get_backend_io(), but well..

It's not necessary per say, but it ensures that pg_stat_get_backend_io() does not
return rows full of "invalid" combinations.

For example:

With the filtering in place, on a "client backend" we would get:

postgres=# select * from pg_stat_get_backend_io(pg_backend_pid());
  backend_type  |    object     |  context  | reads | read_time | writes | write_time | writebacks | writeback_time | extends | extend_time | op_bytes | hits | evictions | reuses | fsyncs | fsync_time | stats_reset
----------------+---------------+-----------+-------+-----------+--------+------------+------------+----------------+---------+-------------+----------+------+-----------+--------+--------+------------+-------------
 client backend | relation      | bulkread  |     0 |         0 |      0 |          0 |          0 |              0 |         |             |     8192 |    0 |         0 |      0 |        |            |
 client backend | relation      | bulkwrite |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 client backend | relation      | normal    |    86 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 | 1604 |         0 |        |      0 |          0 |
 client backend | relation      | vacuum    |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 client backend | temp relation | normal    |     0 |         0 |      0 |          0 |            |                |       0 |           0 |     8192 |    0 |         0 |        |        |            |
(5 rows)

and for example for the walsender:

postgres=# select * from pg_stat_get_backend_io(3982910);
 backend_type |    object     |  context  | reads | read_time | writes | write_time | writebacks | writeback_time | extends | extend_time | op_bytes | hits | evictions | reuses | fsyncs | fsync_time | stats_reset
--------------+---------------+-----------+-------+-----------+--------+------------+------------+----------------+---------+-------------+----------+------+-----------+--------+--------+------------+-------------
 walsender    | relation      | bulkread  |     0 |         0 |      0 |          0 |          0 |              0 |         |             |     8192 |    0 |         0 |      0 |        |            |
 walsender    | relation      | bulkwrite |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 walsender    | relation      | normal    |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |   54 |         0 |        |      0 |          0 |
 walsender    | relation      | vacuum    |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 walsender    | temp relation | normal    |     0 |         0 |      0 |          0 |            |                |       0 |           0 |     8192 |    0 |         0 |        |        |            |
(5 rows)

While, without the filtering we would get:

postgres=# select * from pg_stat_get_backend_io(pg_backend_pid());
  backend_type  |    object     |  context  | reads | read_time | writes | write_time | writebacks | writeback_time | extends | extend_time | op_bytes | hits | evictions | reuses | fsyncs | fsync_time | stats_reset
----------------+---------------+-----------+-------+-----------+--------+------------+------------+----------------+---------+-------------+----------+------+-----------+--------+--------+------------+-------------
 client backend | relation      | bulkread  |     0 |         0 |      0 |          0 |          0 |              0 |         |             |     8192 |    0 |         0 |      0 |        |            |
 client backend | relation      | bulkwrite |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 client backend | relation      | normal    |     4 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |   50 |         0 |        |      0 |          0 |
 client backend | relation      | vacuum    |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 client backend | temp relation | bulkread  |       |           |        |            |            |                |         |             |     8192 |      |           |        |        |            |
 client backend | temp relation | bulkwrite |       |           |        |            |            |                |         |             |     8192 |      |           |        |        |            |
 client backend | temp relation | normal    |     0 |         0 |      0 |          0 |            |                |       0 |           0 |     8192 |    0 |         0 |        |        |            |
 client backend | temp relation | vacuum    |       |           |        |            |            |                |         |             |     8192 |      |           |        |        |            |
(8 rows)

and for a walsender:

postgres=# select * from pg_stat_get_backend_io(3981588);
 backend_type |    object     |  context  | reads | read_time | writes | write_time | writebacks | writeback_time | extends | extend_time | op_bytes | hits | evictions | reuses | fsyncs | fsync_time | stats_reset
--------------+---------------+-----------+-------+-----------+--------+------------+------------+----------------+---------+-------------+----------+------+-----------+--------+--------+------------+-------------
 walsender    | relation      | bulkread  |     0 |         0 |      0 |          0 |          0 |              0 |         |             |     8192 |    0 |         0 |      0 |        |            |
 walsender    | relation      | bulkwrite |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 walsender    | relation      | normal    |     6 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |   48 |         0 |        |      0 |          0 |
 walsender    | relation      | vacuum    |     0 |         0 |      0 |          0 |          0 |              0 |       0 |           0 |     8192 |    0 |         0 |      0 |        |            |
 walsender    | temp relation | bulkread  |       |           |        |            |            |                |         |             |     8192 |      |           |        |        |            |
 walsender    | temp relation | bulkwrite |       |           |        |            |            |                |         |             |     8192 |      |           |        |        |            |
 walsender    | temp relation | normal    |     0 |         0 |      0 |          0 |            |                |       0 |           0 |     8192 |    0 |         0 |        |        |            |
 walsender    | temp relation | vacuum    |       |           |        |            |            |                |         |             |     8192 |      |           |        |        |            |
(8 rows)

It would not be possible to remove those "extra 3 rows" with a join on the
backend type on pg_stat_activity because only the C code knows about what the
compatibility is (though pgstat_tracks_io_object).

> +	/* drop the backend stats entry */
> +	pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber);
> 
> Oh, I've missed something.  Shouldn't pgstat_request_entry_refs_gc()
> be called when this returns false?

I'm not sure that would be possible for a PGSTAT_KIND_BACKEND kind to return
false here but I agree that's better to call pgstat_request_entry_refs_gc()
if that's the case. Done in v9 attached.

> The creation of the dshash entry is a bit too early, I think..  How
> about delaying it more so as we don't create entries that could be
> useless if we fail the last steps of authentication?  One spot would
> be to delay the creation of the new entry at the end of
> pgstat_bestart(), where we know that we are done with authentication
> and that the backend is ready to report back to the client connected
> to it.  It is true that some subsystems could produce stats as of the 
> early transactions they generate, which is why pgstat_initialize() is
> done that early in BaseInit(), but that's not really relevant for this
> case?

I think that makes sense to move the stats entry creation in pgstat_bestart(),
done that way in v9.

> I'm still feeling a bit uneasy about the drop done in
> pgstat_shutdown_hook(); it would be nice to make sure that this
> happens in a path that would run just after we're done with the
> creation of the entry to limit windows where we have an entry but no
> way to drop it, or vice-versa,

I don't believe that's possible as MyProcNumber can't be "reused" (and is
guaranteed to remain valid) until ProcKill() is done (which happens after the
pgstat_shutdown_hook()).

> Perhaps there's an argument for an entirely separate
> callback that would run before pgstat is plugged off, like a new
> before_shmem_exit() callback registered after the entry is created?

As the ProcKill() is run in shmem_exit() (and so after before_shmem_exit()),
I think that the way we currently drop the backend stats entry is fine (unless
I miss something about your concern).

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com


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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-17 06:26           ` Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Michael Paquier @ 2024-12-17 06:26 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Mon, Dec 16, 2024 at 03:42:04PM +0000, Bertrand Drouvot wrote:
> It's not necessary per say, but it ensures that pg_stat_get_backend_io() does not
> return rows full of "invalid" combinations.

Okay.  I am not planning to fight more over this point.

> I'm not sure that would be possible for a PGSTAT_KIND_BACKEND kind to return
> false here but I agree that's better to call pgstat_request_entry_refs_gc()
> if that's the case. Done in v9 attached.

It seems to me that it could be possible if a different backend keeps
a reference to this entry.

>> Perhaps there's an argument for an entirely separate
>> callback that would run before pgstat is plugged off, like a new
>> before_shmem_exit() callback registered after the entry is created?
> 
> As the ProcKill() is run in shmem_exit() (and so after before_shmem_exit()),
> I think that the way we currently drop the backend stats entry is fine (unless
> I miss something about your concern).

Looking closely at this one, I think that you're right as long as the
shutdown callback is registered before the entry is created.  So I'm
OK because the drop is a no-op if the entry cannot be found, and we
would not try a pgstat_request_entry_refs_gc().

Anyway, I have put my hands on v9.  A couple of notes, while hacking
through it.  See v9-0002 for the parts I have modified, that applies
on top of your v9-0001.

You may have noticed fee2b3ea2ecdg, which led me to fix two comments
as these paths are not related to only database objects.

Added more documentation, tweaked quite a bit the comments, a bit less
the docs (no need for the two linkends) and applied an indentation.

pg_stat_get_backend_io() can be simpler, and does not need the loop
with the extra LocalPgBackendStatus.  It is possible to call once
pgstat_get_beentry_by_proc_number() and retrieve the PID and the
bktype for the two sanity checks we want to do on them.

s/pgstat_fetch_proc_stat_io/pgstat_fetch_stat_backend/.
s/pgstat_create_backend_stat/pgstat_create_backend/.

I've been wondering for quite a bit about PgStat_BackendPendingIO and
PgStat_PendingIO, and concluded to define both in pgstat.h, with the
former being defined based on the latter to keep the dependency
between both at the same place.
--
Michael


Attachments:

  [text/x-diff] v9-0001-per-backend-I-O-statistics.patch (36.5K, ../../[email protected]/2-v9-0001-per-backend-I-O-statistics.patch)
  download | inline diff:
From d0df4be6cd052fc91970ec99933268f9b55940b7 Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Mon, 28 Oct 2024 12:50:32 +0000
Subject: [PATCH v9 1/2] per backend I/O statistics

While pg_stat_io provides cluster-wide I/O statistics, this commit adds the
ability to track and display per backend I/O statistics.

It adds a new statistics kind and 2 new functions:

- pg_stat_reset_backend_stats() to be able to reset the stats for a given
backend pid.
- pg_stat_get_backend_io() to retrieve I/O statistics for a given backend pid.

The new KIND is named PGSTAT_KIND_BACKEND as it could be used in the future
to store other statistics (than the I/O ones) per backend. The new KIND is
a variable-numbered one and has an automatic cap on the maximum number of
entries (as its hash key contains the proc number).

There is no need to write the per backend I/O stats to disk (no point to
see stats for backends that do not exist anymore after a re-start), so using
"write_to_file = false".

Note that per backend I/O statistics are not collected for the checkpointer,
the background writer, the startup process and the autovacuum launcher as those
are already visible in pg_stat_io and there is only one of those.

XXX: Bump catalog version needs to be done.
---
 src/include/catalog/pg_proc.dat              |  14 ++
 src/include/pgstat.h                         |  31 +++-
 src/include/utils/pgstat_internal.h          |  14 ++
 src/backend/catalog/system_functions.sql     |   2 +
 src/backend/utils/activity/Makefile          |   1 +
 src/backend/utils/activity/backend_status.c  |   4 +
 src/backend/utils/activity/meson.build       |   1 +
 src/backend/utils/activity/pgstat.c          |  21 +++
 src/backend/utils/activity/pgstat_backend.c  | 182 +++++++++++++++++++
 src/backend/utils/activity/pgstat_io.c       |  25 ++-
 src/backend/utils/activity/pgstat_relation.c |   2 +
 src/backend/utils/adt/pgstatfuncs.c          | 169 +++++++++++++++++
 src/test/regress/expected/stats.out          |  72 +++++++-
 src/test/regress/sql/stats.sql               |  38 +++-
 doc/src/sgml/config.sgml                     |   8 +-
 doc/src/sgml/monitoring.sgml                 |  37 ++++
 src/tools/pgindent/typedefs.list             |   3 +
 17 files changed, 604 insertions(+), 20 deletions(-)
 create mode 100644 src/backend/utils/activity/pgstat_backend.c

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..437157ffa3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5913,6 +5913,15 @@
   proargnames => '{backend_type,object,context,reads,read_time,writes,write_time,writebacks,writeback_time,extends,extend_time,op_bytes,hits,evictions,reuses,fsyncs,fsync_time,stats_reset}',
   prosrc => 'pg_stat_get_io' },
 
+{ oid => '8806', descr => 'statistics: backend IO statistics',
+  proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => 'int4',
+  proallargtypes => '{int4,text,text,text,int8,float8,int8,float8,int8,float8,int8,float8,int8,int8,int8,int8,int8,float8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{backend_pid,backend_type,object,context,reads,read_time,writes,write_time,writebacks,writeback_time,extends,extend_time,op_bytes,hits,evictions,reuses,fsyncs,fsync_time,stats_reset}',
+  prosrc => 'pg_stat_get_backend_io' },
+
 { oid => '1136', descr => 'statistics: information about WAL activity',
   proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's',
   proparallel => 'r', prorettype => 'record', proargtypes => '',
@@ -6052,6 +6061,11 @@
   proname => 'pg_stat_reset_single_function_counters', provolatile => 'v',
   prorettype => 'void', proargtypes => 'oid',
   prosrc => 'pg_stat_reset_single_function_counters' },
+{ oid => '9987',
+  descr => 'statistics: reset statistics for a single backend',
+  proname => 'pg_stat_reset_backend_stats', provolatile => 'v',
+  prorettype => 'void', proargtypes => 'int4',
+  prosrc => 'pg_stat_reset_backend_stats' },
 { oid => '2307',
   descr => 'statistics: reset collected statistics for a single SLRU',
   proname => 'pg_stat_reset_slru', proisstrict => 'f', provolatile => 'v',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index ebfeef2f46..479773cfd2 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -49,14 +49,15 @@
 #define PGSTAT_KIND_FUNCTION	3	/* per-function statistics */
 #define PGSTAT_KIND_REPLSLOT	4	/* per-slot statistics */
 #define PGSTAT_KIND_SUBSCRIPTION	5	/* per-subscription statistics */
+#define PGSTAT_KIND_BACKEND	6	/* per-backend statistics */
 
 /* stats for fixed-numbered objects */
-#define PGSTAT_KIND_ARCHIVER	6
-#define PGSTAT_KIND_BGWRITER	7
-#define PGSTAT_KIND_CHECKPOINTER	8
-#define PGSTAT_KIND_IO	9
-#define PGSTAT_KIND_SLRU	10
-#define PGSTAT_KIND_WAL	11
+#define PGSTAT_KIND_ARCHIVER	7
+#define PGSTAT_KIND_BGWRITER	8
+#define PGSTAT_KIND_CHECKPOINTER	9
+#define PGSTAT_KIND_IO	10
+#define PGSTAT_KIND_SLRU	11
+#define PGSTAT_KIND_WAL	12
 
 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
 #define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
@@ -362,12 +363,23 @@ typedef struct PgStat_BktypeIO
 	PgStat_Counter times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 } PgStat_BktypeIO;
 
+typedef struct PgStat_BackendPendingIO
+{
+	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
+	instr_time	pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
+} PgStat_BackendPendingIO;
+
 typedef struct PgStat_IO
 {
 	TimestampTz stat_reset_timestamp;
 	PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
 } PgStat_IO;
 
+typedef struct PgStat_Backend
+{
+	TimestampTz stat_reset_timestamp;
+	PgStat_BktypeIO stats;
+} PgStat_Backend;
 
 typedef struct PgStat_StatDBEntry
 {
@@ -549,6 +561,13 @@ extern bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid);
 extern void pgstat_report_archiver(const char *xlog, bool failed);
 extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
 
+/*
+ * Functions in pgstat_backend.c
+ */
+
+extern PgStat_Backend *pgstat_fetch_proc_stat_io(ProcNumber procNumber);
+extern bool pgstat_tracks_backend_bktype(BackendType bktype);
+extern void pgstat_create_backend_stat(ProcNumber procnum);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 7338bc1e28..811ed9b005 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -450,6 +450,11 @@ typedef struct PgStatShared_ReplSlot
 	PgStat_StatReplSlotEntry stats;
 } PgStatShared_ReplSlot;
 
+typedef struct PgStatShared_Backend
+{
+	PgStatShared_Common header;
+	PgStat_Backend stats;
+} PgStatShared_Backend;
 
 /*
  * Central shared memory entry for the cumulative stats system.
@@ -604,6 +609,15 @@ extern void pgstat_archiver_init_shmem_cb(void *stats);
 extern void pgstat_archiver_reset_all_cb(TimestampTz ts);
 extern void pgstat_archiver_snapshot_cb(void);
 
+/*
+ * Functions in pgstat_backend.c
+ */
+
+extern void pgstat_flush_backend(bool nowait);
+
+extern PgStat_BackendPendingIO *pgstat_prep_backend_pending(ProcNumber procnum);
+extern bool pgstat_backend_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_backend_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index c51dfca802..14eb99cd47 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -711,6 +711,8 @@ REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_table_counters(oid) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM public;
 
+REVOKE EXECUTE ON FUNCTION pg_stat_reset_backend_stats(integer) FROM public;
+
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_replication_slot(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_have_stats(text, oid, int8) FROM public;
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index b9fd66ea17..24b64a2742 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -20,6 +20,7 @@ OBJS = \
 	backend_status.o \
 	pgstat.o \
 	pgstat_archiver.o \
+	pgstat_backend.o \
 	pgstat_bgwriter.o \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index 22c6dc378c..e9c3b87ac2 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -426,6 +426,10 @@ pgstat_bestart(void)
 
 	PGSTAT_END_WRITE_ACTIVITY(vbeentry);
 
+	/* Create the backend statistics entry */
+	if (pgstat_tracks_backend_bktype(MyBackendType))
+		pgstat_create_backend_stat(MyProcNumber);
+
 	/* Update app name to current GUC setting */
 	if (application_name)
 		pgstat_report_appname(application_name);
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index f73c22905c..380d3dd70c 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -5,6 +5,7 @@ backend_sources += files(
   'backend_status.c',
   'pgstat.c',
   'pgstat_archiver.c',
+  'pgstat_backend.c',
   'pgstat_bgwriter.c',
   'pgstat_checkpointer.c',
   'pgstat_database.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index b4e357c8a4..b72c779b2c 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -77,6 +77,7 @@
  *
  * Each statistics kind is handled in a dedicated file:
  * - pgstat_archiver.c
+ * - pgstat_backend.c
  * - pgstat_bgwriter.c
  * - pgstat_checkpointer.c
  * - pgstat_database.c
@@ -358,6 +359,22 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
 		.reset_timestamp_cb = pgstat_subscription_reset_timestamp_cb,
 	},
 
+	[PGSTAT_KIND_BACKEND] = {
+		.name = "backend",
+
+		.fixed_amount = false,
+		.write_to_file = false,
+
+		.accessed_across_databases = true,
+
+		.shared_size = sizeof(PgStatShared_Backend),
+		.shared_data_off = offsetof(PgStatShared_Backend, stats),
+		.shared_data_len = sizeof(((PgStatShared_Backend *) 0)->stats),
+		.pending_size = sizeof(PgStat_BackendPendingIO),
+
+		.flush_pending_cb = pgstat_backend_flush_cb,
+		.reset_timestamp_cb = pgstat_backend_reset_timestamp_cb,
+	},
 
 	/* stats for fixed-numbered (mostly 1) objects */
 
@@ -602,6 +619,10 @@ pgstat_shutdown_hook(int code, Datum arg)
 	Assert(dlist_is_empty(&pgStatPending));
 	dlist_init(&pgStatPending);
 
+	/* drop the backend stats entry */
+	if (!pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber))
+		pgstat_request_entry_refs_gc();
+
 	pgstat_detach_shmem();
 
 #ifdef USE_ASSERT_CHECKING
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
new file mode 100644
index 0000000000..e2d83024c2
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -0,0 +1,182 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_backend.c
+ *	  Implementation of backend statistics.
+ *
+ * This file contains the implementation of backend statistics. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of statistics.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_backend.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "utils/pgstat_internal.h"
+
+/*
+ * Returns backend's IO stats.
+ */
+PgStat_Backend *
+pgstat_fetch_proc_stat_io(ProcNumber procNumber)
+{
+	PgStat_Backend *backend_entry;
+
+	backend_entry = (PgStat_Backend *) pgstat_fetch_entry(PGSTAT_KIND_BACKEND,
+														  InvalidOid, procNumber);
+
+	return backend_entry;
+}
+
+/*
+ * Flush out locally pending backend statistics
+ *
+ * If no stats have been recorded, this function returns false.
+ */
+bool
+pgstat_backend_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+	PgStatShared_Backend *shbackendioent;
+	PgStat_BackendPendingIO *pendingent;
+	PgStat_BktypeIO *bktype_shstats;
+
+	if (!pgstat_lock_entry(entry_ref, nowait))
+		return false;
+
+	shbackendioent = (PgStatShared_Backend *) entry_ref->shared_stats;
+	bktype_shstats = &shbackendioent->stats.stats;
+	pendingent = (PgStat_BackendPendingIO *) entry_ref->pending;
+
+	for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
+	{
+		for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+		{
+			for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+			{
+				instr_time	time;
+
+				bktype_shstats->counts[io_object][io_context][io_op] +=
+					pendingent->counts[io_object][io_context][io_op];
+
+				time = pendingent->pending_times[io_object][io_context][io_op];
+
+				bktype_shstats->times[io_object][io_context][io_op] +=
+					INSTR_TIME_GET_MICROSEC(time);
+			}
+		}
+	}
+
+	pgstat_unlock_entry(entry_ref);
+
+	return true;
+}
+
+/*
+ * Simpler wrapper of pgstat_backend_flush_cb()
+ */
+void
+pgstat_flush_backend(bool nowait)
+{
+	if (pgstat_tracks_backend_bktype(MyBackendType))
+	{
+		PgStat_EntryRef *entry_ref;
+
+		entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_BACKEND, InvalidOid,
+										 MyProcNumber, false, NULL);
+		(void) pgstat_backend_flush_cb(entry_ref, nowait);
+	}
+}
+
+/*
+ * Create the backend statistics entry for procnum.
+ */
+void
+pgstat_create_backend_stat(ProcNumber procnum)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_Backend *shstatent;
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_BACKEND, InvalidOid,
+										  procnum, NULL);
+
+	shstatent = (PgStatShared_Backend *) entry_ref->shared_stats;
+
+	/*
+	 * NB: need to accept that there might be stats from an older backend,
+	 * e.g. if we previously used this proc number.
+	 */
+	memset(&shstatent->stats, 0, sizeof(shstatent->stats));
+}
+
+/*
+ * Find or create a local PgStat_BackendPendingIO entry for procnum.
+ */
+PgStat_BackendPendingIO *
+pgstat_prep_backend_pending(ProcNumber procnum)
+{
+	PgStat_EntryRef *entry_ref;
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_BACKEND, InvalidOid,
+										  procnum, NULL);
+
+	return entry_ref->pending;
+}
+
+/*
+ * Backend statistics are not collected for all BackendTypes.
+ *
+ * The following BackendTypes do not participate in the backend stats
+ * subsystem:
+ * - The same and for the same reasons as in pgstat_tracks_io_bktype().
+ * - B_BG_WRITER, B_CHECKPOINTER, B_STARTUP and B_AUTOVAC_LAUNCHER because their
+ * I/O stats are already visible in pg_stat_io and there is only one of those.
+ *
+ * Function returns true if BackendType participates in the backend stats
+ * subsystem for IO and false if it does not.
+ *
+ * When adding a new BackendType, also consider adding relevant restrictions to
+ * pgstat_tracks_io_object() and pgstat_tracks_io_op().
+ */
+bool
+pgstat_tracks_backend_bktype(BackendType bktype)
+{
+	/*
+	 * List every type so that new backend types trigger a warning about
+	 * needing to adjust this switch.
+	 */
+	switch (bktype)
+	{
+		case B_INVALID:
+		case B_AUTOVAC_LAUNCHER:
+		case B_DEAD_END_BACKEND:
+		case B_ARCHIVER:
+		case B_LOGGER:
+		case B_WAL_RECEIVER:
+		case B_WAL_WRITER:
+		case B_WAL_SUMMARIZER:
+		case B_BG_WRITER:
+		case B_CHECKPOINTER:
+		case B_STARTUP:
+			return false;
+
+		case B_AUTOVAC_WORKER:
+		case B_BACKEND:
+		case B_BG_WORKER:
+		case B_STANDALONE_BACKEND:
+		case B_SLOTSYNC_WORKER:
+		case B_WAL_SENDER:
+			return true;
+	}
+
+	return false;
+}
+
+void
+pgstat_backend_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
+{
+	((PgStatShared_Backend *) header)->stats.stat_reset_timestamp = ts;
+}
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index f9883af2b3..e0c206a453 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -20,13 +20,7 @@
 #include "storage/bufmgr.h"
 #include "utils/pgstat_internal.h"
 
-
-typedef struct PgStat_PendingIO
-{
-	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-	instr_time	pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-} PgStat_PendingIO;
-
+typedef PgStat_BackendPendingIO PgStat_PendingIO;
 
 static PgStat_PendingIO PendingIOStats;
 static bool have_iostats = false;
@@ -87,6 +81,14 @@ pgstat_count_io_op_n(IOObject io_object, IOContext io_context, IOOp io_op, uint3
 	Assert((unsigned int) io_op < IOOP_NUM_TYPES);
 	Assert(pgstat_tracks_io_op(MyBackendType, io_object, io_context, io_op));
 
+	if (pgstat_tracks_backend_bktype(MyBackendType))
+	{
+		PgStat_PendingIO *entry_ref;
+
+		entry_ref = pgstat_prep_backend_pending(MyProcNumber);
+		entry_ref->counts[io_object][io_context][io_op] += cnt;
+	}
+
 	PendingIOStats.counts[io_object][io_context][io_op] += cnt;
 
 	have_iostats = true;
@@ -148,6 +150,15 @@ pgstat_count_io_op_time(IOObject io_object, IOContext io_context, IOOp io_op,
 
 		INSTR_TIME_ADD(PendingIOStats.pending_times[io_object][io_context][io_op],
 					   io_time);
+
+		if (pgstat_tracks_backend_bktype(MyBackendType))
+		{
+			PgStat_PendingIO *entry_ref;
+
+			entry_ref = pgstat_prep_backend_pending(MyProcNumber);
+			INSTR_TIME_ADD(entry_ref->pending_times[io_object][io_context][io_op],
+						   io_time);
+		}
 	}
 
 	pgstat_count_io_op_n(io_object, io_context, io_op, cnt);
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index faba8b64d2..85e65557bb 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -264,6 +264,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	 * VACUUM command has processed all tables and committed.
 	 */
 	pgstat_flush_io(false);
+	pgstat_flush_backend(false);
 }
 
 /*
@@ -350,6 +351,7 @@ pgstat_report_analyze(Relation rel,
 
 	/* see pgstat_report_vacuum() */
 	pgstat_flush_io(false);
+	pgstat_flush_backend(false);
 }
 
 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index cdf37403e9..b939551d36 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1474,6 +1474,154 @@ pg_stat_get_io(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+Datum
+pg_stat_get_backend_io(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo;
+	PgStat_Backend *backend_stats;
+	Datum		bktype_desc;
+	PgStat_BktypeIO *bktype_stats;
+	BackendType bktype;
+	Datum		reset_time;
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid;
+	PGPROC	   *proc;
+	ProcNumber	procNumber;
+
+	InitMaterializedSRF(fcinfo, 0);
+	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	pid = PG_GETARG_INT32(0);
+	proc = BackendPidGetProc(pid);
+
+	/*
+	 * Could be an auxiliary process but would not report any stats due to
+	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
+	 * AuxiliaryPidGetProc().
+	 */
+	if (!proc)
+		return (Datum) 0;
+
+	procNumber = GetNumberFromPGProc(proc);
+	backend_stats = pgstat_fetch_proc_stat_io(procNumber);
+
+	if (!backend_stats)
+		return (Datum) 0;
+
+	bktype = B_INVALID;
+
+	/* Look for the backend type */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* Looking for specific PID, ignore all the others */
+		if (beentry->st_procpid != pid)
+			continue;
+
+		bktype = beentry->st_backendType;
+		break;
+	}
+
+	/* Backend is gone */
+	if (bktype == B_INVALID)
+		return (Datum) 0;
+
+	bktype_desc = CStringGetTextDatum(GetBackendTypeDesc(bktype));
+	bktype_stats = &backend_stats->stats;
+	reset_time = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+
+	/*
+	 * In Assert builds, we can afford an extra loop through all of the
+	 * counters checking that only expected stats are non-zero, since it keeps
+	 * the non-Assert code cleaner.
+	 */
+	Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype));
+
+	for (int io_obj = 0; io_obj < IOOBJECT_NUM_TYPES; io_obj++)
+	{
+		const char *obj_name = pgstat_get_io_object_name(io_obj);
+
+		for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+		{
+			const char *context_name = pgstat_get_io_context_name(io_context);
+
+			Datum		values[IO_NUM_COLUMNS] = {0};
+			bool		nulls[IO_NUM_COLUMNS] = {0};
+
+			/*
+			 * Some combinations of BackendType, IOObject, and IOContext are
+			 * not valid for any type of IOOp. In such cases, omit the entire
+			 * row from the view.
+			 */
+			if (!pgstat_tracks_io_object(bktype, io_obj, io_context))
+				continue;
+
+			values[IO_COL_BACKEND_TYPE] = bktype_desc;
+			values[IO_COL_CONTEXT] = CStringGetTextDatum(context_name);
+			values[IO_COL_OBJECT] = CStringGetTextDatum(obj_name);
+			if (backend_stats->stat_reset_timestamp != 0)
+				values[IO_COL_RESET_TIME] = reset_time;
+			else
+				nulls[IO_COL_RESET_TIME] = true;
+
+			/*
+			 * Hard-code this to the value of BLCKSZ for now. Future values
+			 * could include XLOG_BLCKSZ, once WAL IO is tracked, and constant
+			 * multipliers, once non-block-oriented IO (e.g. temporary file
+			 * IO) is tracked.
+			 */
+			values[IO_COL_CONVERSION] = Int64GetDatum(BLCKSZ);
+
+			for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+			{
+				int			op_idx = pgstat_get_io_op_index(io_op);
+				int			time_idx = pgstat_get_io_time_index(io_op);
+
+				/*
+				 * Some combinations of BackendType and IOOp, of IOContext and
+				 * IOOp, and of IOObject and IOOp are not tracked. Set these
+				 * cells in the view NULL.
+				 */
+				if (pgstat_tracks_io_op(bktype, io_obj, io_context, io_op))
+				{
+					PgStat_Counter count =
+						bktype_stats->counts[io_obj][io_context][io_op];
+
+					values[op_idx] = Int64GetDatum(count);
+				}
+				else
+					nulls[op_idx] = true;
+
+				/* not every operation is timed */
+				if (time_idx == IO_COL_INVALID)
+					continue;
+
+				if (!nulls[op_idx])
+				{
+					PgStat_Counter time =
+						bktype_stats->times[io_obj][io_context][io_op];
+
+					values[time_idx] = Float8GetDatum(pg_stat_us_to_ms(time));
+				}
+				else
+					nulls[time_idx] = true;
+			}
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+								 values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of WAL activity
  */
@@ -1779,6 +1927,27 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+pg_stat_reset_backend_stats(PG_FUNCTION_ARGS)
+{
+	PGPROC	   *proc;
+	int			backend_pid = PG_GETARG_INT32(0);
+
+	proc = BackendPidGetProc(backend_pid);
+
+	/*
+	 * Could be an auxiliary process but would not report any stats due to
+	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
+	 * AuxiliaryPidGetProc().
+	 */
+	if (!proc)
+		PG_RETURN_VOID();
+
+	pgstat_reset(PGSTAT_KIND_BACKEND, InvalidOid, GetNumberFromPGProc(proc));
+
+	PG_RETURN_VOID();
+}
+
 /* Reset SLRU counters (a specific one or all of them). */
 Datum
 pg_stat_reset_slru(PG_FUNCTION_ARGS)
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 56771f83ed..3447e7b75d 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1249,7 +1249,7 @@ SELECT pg_stat_get_subscription_stats(NULL);
  
 (1 row)
 
--- Test that the following operations are tracked in pg_stat_io:
+-- Test that the following operations are tracked in pg_stat_io and in backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -1259,11 +1259,19 @@ SELECT pg_stat_get_subscription_stats(NULL);
 -- be sure of the state of shared buffers at the point the test is run.
 -- Create a regular table and insert some data to generate IOCONTEXT_NORMAL
 -- extends.
+SELECT pid AS checkpointer_pid FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer' \gset
 SELECT sum(extends) AS io_sum_shared_before_extends
   FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
+SELECT sum(extends) AS my_io_sum_shared_before_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
 SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
   FROM pg_stat_io
   WHERE object = 'relation' \gset io_sum_shared_before_
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_before_
 CREATE TABLE test_io_shared(a int);
 INSERT INTO test_io_shared SELECT i FROM generate_series(1,100)i;
 SELECT pg_stat_force_next_flush();
@@ -1280,8 +1288,17 @@ SELECT :io_sum_shared_after_extends > :io_sum_shared_before_extends;
  t
 (1 row)
 
+SELECT sum(extends) AS my_io_sum_shared_after_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
+SELECT :my_io_sum_shared_after_extends > :my_io_sum_shared_before_extends;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- After a checkpoint, there should be some additional IOCONTEXT_NORMAL writes
--- and fsyncs.
+-- and fsyncs in the global stats (not for the backend).
 -- See comment above for rationale for two explicit CHECKPOINTs.
 CHECKPOINT;
 CHECKPOINT;
@@ -1301,6 +1318,31 @@ SELECT current_setting('fsync') = 'off'
  t
 (1 row)
 
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_after_
+SELECT :my_io_sum_shared_after_writes >= :my_io_sum_shared_before_writes;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT current_setting('fsync') = 'off'
+  OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
+      AND :my_io_sum_shared_after_fsyncs= 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- Don't return any rows if querying other backend's stats that are excluded
+-- from the backend stats collection (like the checkpointer).
+SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
+ ?column? 
+----------
+ t
+(1 row)
+
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
 SELECT sum(reads) AS io_sum_shared_before_reads
@@ -1521,6 +1563,8 @@ SELECT pg_stat_have_stats('io', 0, 0);
 
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset
   FROM pg_stat_io \gset
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_pre_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT pg_stat_reset_shared('io');
  pg_stat_reset_shared 
 ----------------------
@@ -1535,6 +1579,30 @@ SELECT :io_stats_post_reset < :io_stats_pre_reset;
  t
 (1 row)
 
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+-- pg_stat_reset_shared() did not reset backend IO stats
+SELECT :my_io_stats_pre_reset <= :my_io_stats_post_reset;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- but pg_stat_reset_backend_stats() does
+SELECT pg_stat_reset_backend_stats(pg_backend_pid());
+ pg_stat_reset_backend_stats 
+-----------------------------
+ 
+(1 row)
+
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_backend_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- test BRIN index doesn't block HOT update
 CREATE TABLE brin_hot (
   id  integer PRIMARY KEY,
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 7147cc2f89..9c925005be 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -595,7 +595,7 @@ SELECT pg_stat_get_replication_slot(NULL);
 SELECT pg_stat_get_subscription_stats(NULL);
 
 
--- Test that the following operations are tracked in pg_stat_io:
+-- Test that the following operations are tracked in pg_stat_io and in backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -607,20 +607,32 @@ SELECT pg_stat_get_subscription_stats(NULL);
 
 -- Create a regular table and insert some data to generate IOCONTEXT_NORMAL
 -- extends.
+SELECT pid AS checkpointer_pid FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer' \gset
 SELECT sum(extends) AS io_sum_shared_before_extends
   FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
+SELECT sum(extends) AS my_io_sum_shared_before_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
 SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
   FROM pg_stat_io
   WHERE object = 'relation' \gset io_sum_shared_before_
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_before_
 CREATE TABLE test_io_shared(a int);
 INSERT INTO test_io_shared SELECT i FROM generate_series(1,100)i;
 SELECT pg_stat_force_next_flush();
 SELECT sum(extends) AS io_sum_shared_after_extends
   FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
 SELECT :io_sum_shared_after_extends > :io_sum_shared_before_extends;
+SELECT sum(extends) AS my_io_sum_shared_after_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
+SELECT :my_io_sum_shared_after_extends > :my_io_sum_shared_before_extends;
 
 -- After a checkpoint, there should be some additional IOCONTEXT_NORMAL writes
--- and fsyncs.
+-- and fsyncs in the global stats (not for the backend).
 -- See comment above for rationale for two explicit CHECKPOINTs.
 CHECKPOINT;
 CHECKPOINT;
@@ -630,6 +642,17 @@ SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
 SELECT :io_sum_shared_after_writes > :io_sum_shared_before_writes;
 SELECT current_setting('fsync') = 'off'
   OR :io_sum_shared_after_fsyncs > :io_sum_shared_before_fsyncs;
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_after_
+SELECT :my_io_sum_shared_after_writes >= :my_io_sum_shared_before_writes;
+SELECT current_setting('fsync') = 'off'
+  OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
+      AND :my_io_sum_shared_after_fsyncs= 0);
+
+-- Don't return any rows if querying other backend's stats that are excluded
+-- from the backend stats collection (like the checkpointer).
+SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
 
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
@@ -762,10 +785,21 @@ SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_ext
 SELECT pg_stat_have_stats('io', 0, 0);
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset
   FROM pg_stat_io \gset
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_pre_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT pg_stat_reset_shared('io');
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_post_reset
   FROM pg_stat_io \gset
 SELECT :io_stats_post_reset < :io_stats_pre_reset;
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+-- pg_stat_reset_shared() did not reset backend IO stats
+SELECT :my_io_stats_pre_reset <= :my_io_stats_post_reset;
+-- but pg_stat_reset_backend_stats() does
+SELECT pg_stat_reset_backend_stats(pg_backend_pid());
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_backend_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
 
 
 -- test BRIN index doesn't block HOT update
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 24bd504c21..fbdd6ce574 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8403,9 +8403,11 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         displayed in <link linkend="monitoring-pg-stat-database-view">
         <structname>pg_stat_database</structname></link>,
         <link linkend="monitoring-pg-stat-io-view">
-        <structname>pg_stat_io</structname></link>, in the output of
-        <xref linkend="sql-explain"/> when the <literal>BUFFERS</literal> option
-        is used, in the output of <xref linkend="sql-vacuum"/> when
+        <structname>pg_stat_io</structname></link>, in the output of the
+        <link linkend="pg-stat-get-backend-io">
+        <function>pg_stat_get_backend_io()</function></link> function, in the
+        output of <xref linkend="sql-explain"/> when the <literal>BUFFERS</literal>
+        option is used, in the output of <xref linkend="sql-vacuum"/> when
         the <literal>VERBOSE</literal> option is used, by autovacuum
         for auto-vacuums and auto-analyzes, when <xref
         linkend="guc-log-autovacuum-min-duration"/> is set and by
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 840d7f8161..4a9464da61 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -4790,6 +4790,25 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry id="pg-stat-get-backend-io" role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_io</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_io</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns I/O statistics about the backend with the specified
+        process ID. The output fields are exactly the same as the ones in the
+        <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
+        view. The function does not return I/O statistics for the checkpointer,
+        the background writer, the startup process and the autovacuum launcher
+        as they are already visible in the <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
+        view and there is only one of those.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -4971,6 +4990,24 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_reset_backend_stats</primary>
+        </indexterm>
+        <function>pg_stat_reset_backend_stats</function> ( <type>integer</type> )
+        <returnvalue>void</returnvalue>
+       </para>
+       <para>
+        Resets statistics for a single backend with the specified process ID
+        to zero.
+       </para>
+       <para>
+        This function is restricted to superusers by default, but other users
+        can be granted EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ce33e55bf1..398dd92527 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2121,6 +2121,7 @@ PgFdwSamplingMethod
 PgFdwScanState
 PgIfAddrCallback
 PgStatShared_Archiver
+PgStatShared_Backend
 PgStatShared_BgWriter
 PgStatShared_Checkpointer
 PgStatShared_Common
@@ -2136,6 +2137,8 @@ PgStatShared_SLRU
 PgStatShared_Subscription
 PgStatShared_Wal
 PgStat_ArchiverStats
+PgStat_Backend
+PgStat_BackendPendingIO
 PgStat_BackendSubEntry
 PgStat_BgWriterStats
 PgStat_BktypeIO
-- 
2.45.2



  [text/x-diff] v9-0002-Tweaks-on-top-of-v9.patch (14.7K, ../../[email protected]/3-v9-0002-Tweaks-on-top-of-v9.patch)
  download | inline diff:
From 8c7fd33e98ec958308979dba9cb7897787ddc48e Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Tue, 17 Dec 2024 15:15:23 +0900
Subject: [PATCH v9 2/2] Tweaks on top of v9

Other notes for commit:
Requires a bump of CATALOG_VERSION_NO.
Requires a bump of PGSTAT_FILE_FORMAT_ID.
---
 src/include/catalog/pg_proc.dat             |  3 +-
 src/include/pgstat.h                        | 11 +++--
 src/backend/utils/activity/backend_status.c |  2 +-
 src/backend/utils/activity/pgstat_backend.c | 38 ++++++++-------
 src/backend/utils/activity/pgstat_io.c      |  2 -
 src/backend/utils/adt/pgstatfuncs.c         | 53 +++++++++------------
 src/test/regress/expected/stats.out         | 28 +++++++----
 src/test/regress/sql/stats.sql              | 12 +++--
 doc/src/sgml/monitoring.sgml                | 10 ++--
 9 files changed, 86 insertions(+), 73 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 437157ffa3..2dcc2d42da 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6061,8 +6061,7 @@
   proname => 'pg_stat_reset_single_function_counters', provolatile => 'v',
   prorettype => 'void', proargtypes => 'oid',
   prosrc => 'pg_stat_reset_single_function_counters' },
-{ oid => '9987',
-  descr => 'statistics: reset statistics for a single backend',
+{ oid => '8807', descr => 'statistics: reset statistics for a single backend',
   proname => 'pg_stat_reset_backend_stats', provolatile => 'v',
   prorettype => 'void', proargtypes => 'int4',
   prosrc => 'pg_stat_reset_backend_stats' },
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 479773cfd2..56ed1b8bb3 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -363,11 +363,11 @@ typedef struct PgStat_BktypeIO
 	PgStat_Counter times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 } PgStat_BktypeIO;
 
-typedef struct PgStat_BackendPendingIO
+typedef struct PgStat_PendingIO
 {
 	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 	instr_time	pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-} PgStat_BackendPendingIO;
+} PgStat_PendingIO;
 
 typedef struct PgStat_IO
 {
@@ -375,6 +375,9 @@ typedef struct PgStat_IO
 	PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
 } PgStat_IO;
 
+/* Backend statistics store the same amount of IO data as PGSTAT_KIND_IO */
+typedef PgStat_PendingIO PgStat_BackendPendingIO;
+
 typedef struct PgStat_Backend
 {
 	TimestampTz stat_reset_timestamp;
@@ -565,9 +568,9 @@ extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
  * Functions in pgstat_backend.c
  */
 
-extern PgStat_Backend *pgstat_fetch_proc_stat_io(ProcNumber procNumber);
+extern PgStat_Backend *pgstat_fetch_stat_backend(ProcNumber procNumber);
 extern bool pgstat_tracks_backend_bktype(BackendType bktype);
-extern void pgstat_create_backend_stat(ProcNumber procnum);
+extern void pgstat_create_backend(ProcNumber procnum);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index e9c3b87ac2..bf33e33a4e 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -428,7 +428,7 @@ pgstat_bestart(void)
 
 	/* Create the backend statistics entry */
 	if (pgstat_tracks_backend_bktype(MyBackendType))
-		pgstat_create_backend_stat(MyProcNumber);
+		pgstat_create_backend(MyProcNumber);
 
 	/* Update app name to current GUC setting */
 	if (application_name)
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index e2d83024c2..6b2c9baa8c 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -3,11 +3,17 @@
  * pgstat_backend.c
  *	  Implementation of backend statistics.
  *
- * This file contains the implementation of backend statistics. It is kept
+ * This file contains the implementation of backend statistics.  It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
- * storage implementation and the details about individual types of statistics.
+ * storage implementation and the details about individual types of
+ * statistics.
  *
- * Copyright (c) 2024, PostgreSQL Global Development Group
+ * This statistics kind uses a proc number as object ID for the hash table
+ * of pgstats.  Entries are created each time a process is spawned, and are
+ * dropped when the process exits.  These are not written to the pgstats file
+ * on disk.
+ *
+ * Copyright (c) 2001-2024, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
  *	  src/backend/utils/activity/pgstat_backend.c
@@ -19,10 +25,10 @@
 #include "utils/pgstat_internal.h"
 
 /*
- * Returns backend's IO stats.
+ * Returns statistics of a backend by proc number.
  */
 PgStat_Backend *
-pgstat_fetch_proc_stat_io(ProcNumber procNumber)
+pgstat_fetch_stat_backend(ProcNumber procNumber)
 {
 	PgStat_Backend *backend_entry;
 
@@ -81,21 +87,21 @@ pgstat_backend_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 void
 pgstat_flush_backend(bool nowait)
 {
-	if (pgstat_tracks_backend_bktype(MyBackendType))
-	{
-		PgStat_EntryRef *entry_ref;
+	PgStat_EntryRef *entry_ref;
 
-		entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_BACKEND, InvalidOid,
-										 MyProcNumber, false, NULL);
-		(void) pgstat_backend_flush_cb(entry_ref, nowait);
-	}
+	if (!pgstat_tracks_backend_bktype(MyBackendType))
+		return;
+
+	entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_BACKEND, InvalidOid,
+									 MyProcNumber, false, NULL);
+	(void) pgstat_backend_flush_cb(entry_ref, nowait);
 }
 
 /*
- * Create the backend statistics entry for procnum.
+ * Create backend statistics entry for proc number.
  */
 void
-pgstat_create_backend_stat(ProcNumber procnum)
+pgstat_create_backend(ProcNumber procnum)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Backend *shstatent;
@@ -113,7 +119,7 @@ pgstat_create_backend_stat(ProcNumber procnum)
 }
 
 /*
- * Find or create a local PgStat_BackendPendingIO entry for procnum.
+ * Find or create a local PgStat_BackendPendingIO entry for proc number.
  */
 PgStat_BackendPendingIO *
 pgstat_prep_backend_pending(ProcNumber procnum)
@@ -136,7 +142,7 @@ pgstat_prep_backend_pending(ProcNumber procnum)
  * I/O stats are already visible in pg_stat_io and there is only one of those.
  *
  * Function returns true if BackendType participates in the backend stats
- * subsystem for IO and false if it does not.
+ * subsystem and false if it does not.
  *
  * When adding a new BackendType, also consider adding relevant restrictions to
  * pgstat_tracks_io_object() and pgstat_tracks_io_op().
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index e0c206a453..011a3326da 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -20,8 +20,6 @@
 #include "storage/bufmgr.h"
 #include "utils/pgstat_internal.h"
 
-typedef PgStat_BackendPendingIO PgStat_PendingIO;
-
 static PgStat_PendingIO PendingIOStats;
 static bool have_iostats = false;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index b939551d36..640e687604 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1474,20 +1474,22 @@ pg_stat_get_io(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+/*
+ * Returns I/O statistics for a backend with given PID.
+ */
 Datum
 pg_stat_get_backend_io(PG_FUNCTION_ARGS)
 {
 	ReturnSetInfo *rsinfo;
-	PgStat_Backend *backend_stats;
 	Datum		bktype_desc;
-	PgStat_BktypeIO *bktype_stats;
 	BackendType bktype;
 	Datum		reset_time;
-	int			num_backends = pgstat_fetch_stat_numbackends();
-	int			curr_backend;
 	int			pid;
 	PGPROC	   *proc;
 	ProcNumber	procNumber;
+	PgStat_Backend *backend_stats;
+	PgStat_BktypeIO *bktype_stats;
+	PgBackendStatus *beentry;
 
 	InitMaterializedSRF(fcinfo, 0);
 	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
@@ -1496,40 +1498,28 @@ pg_stat_get_backend_io(PG_FUNCTION_ARGS)
 	proc = BackendPidGetProc(pid);
 
 	/*
-	 * Could be an auxiliary process but would not report any stats due to
-	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
-	 * AuxiliaryPidGetProc().
+	 * This could be an auxiliary process but these do not report backend
+	 * statistics due to pgstat_tracks_backend_bktype(), so there is no need
+	 * for an extra call to AuxiliaryPidGetProc().
 	 */
 	if (!proc)
 		return (Datum) 0;
 
 	procNumber = GetNumberFromPGProc(proc);
-	backend_stats = pgstat_fetch_proc_stat_io(procNumber);
+
+	backend_stats = pgstat_fetch_stat_backend(procNumber);
 
 	if (!backend_stats)
 		return (Datum) 0;
 
-	bktype = B_INVALID;
+	beentry = pgstat_get_beentry_by_proc_number(procNumber);
+	bktype = beentry->st_backendType;
 
-	/* Look for the backend type */
-	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
-	{
-		LocalPgBackendStatus *local_beentry;
-		PgBackendStatus *beentry;
+	/* If PID does not match, leave */
+	if (beentry->st_procpid != pid)
+		return (Datum) 0;
 
-		/* Get the next one in the list */
-		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
-		beentry = &local_beentry->backendStatus;
-
-		/* Looking for specific PID, ignore all the others */
-		if (beentry->st_procpid != pid)
-			continue;
-
-		bktype = beentry->st_backendType;
-		break;
-	}
-
-	/* Backend is gone */
+	/* Backend may be gone, so recheck in case */
 	if (bktype == B_INVALID)
 		return (Datum) 0;
 
@@ -1927,6 +1917,9 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * Reset statistics of backend with given PID.
+ */
 Datum
 pg_stat_reset_backend_stats(PG_FUNCTION_ARGS)
 {
@@ -1936,9 +1929,9 @@ pg_stat_reset_backend_stats(PG_FUNCTION_ARGS)
 	proc = BackendPidGetProc(backend_pid);
 
 	/*
-	 * Could be an auxiliary process but would not report any stats due to
-	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
-	 * AuxiliaryPidGetProc().
+	 * This could be an auxiliary process but these do not report backend
+	 * statistics due to pgstat_tracks_backend_bktype(), so there is no need
+	 * for an extra call to AuxiliaryPidGetProc().
 	 */
 	if (!proc)
 		PG_RETURN_VOID();
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 3447e7b75d..150b6dcf74 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1249,7 +1249,8 @@ SELECT pg_stat_get_subscription_stats(NULL);
  
 (1 row)
 
--- Test that the following operations are tracked in pg_stat_io and in backend stats:
+-- Test that the following operations are tracked in pg_stat_io and in
+-- backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -1335,14 +1336,6 @@ SELECT current_setting('fsync') = 'off'
  t
 (1 row)
 
--- Don't return any rows if querying other backend's stats that are excluded
--- from the backend stats collection (like the checkpointer).
-SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
- ?column? 
-----------
- t
-(1 row)
-
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
 SELECT sum(reads) AS io_sum_shared_before_reads
@@ -1603,6 +1596,23 @@ SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
  t
 (1 row)
 
+-- Check invalid input for pg_stat_get_backend_io()
+SELECT pg_stat_get_backend_io(NULL);
+ pg_stat_get_backend_io 
+------------------------
+(0 rows)
+
+SELECT pg_stat_get_backend_io(0);
+ pg_stat_get_backend_io 
+------------------------
+(0 rows)
+
+-- Auxiliary processes return no data.
+SELECT pg_stat_get_backend_io(:checkpointer_pid);
+ pg_stat_get_backend_io 
+------------------------
+(0 rows)
+
 -- test BRIN index doesn't block HOT update
 CREATE TABLE brin_hot (
   id  integer PRIMARY KEY,
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 9c925005be..1e7d0ff665 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -595,7 +595,8 @@ SELECT pg_stat_get_replication_slot(NULL);
 SELECT pg_stat_get_subscription_stats(NULL);
 
 
--- Test that the following operations are tracked in pg_stat_io and in backend stats:
+-- Test that the following operations are tracked in pg_stat_io and in
+-- backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -650,10 +651,6 @@ SELECT current_setting('fsync') = 'off'
   OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
       AND :my_io_sum_shared_after_fsyncs= 0);
 
--- Don't return any rows if querying other backend's stats that are excluded
--- from the backend stats collection (like the checkpointer).
-SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
-
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
 SELECT sum(reads) AS io_sum_shared_before_reads
@@ -801,6 +798,11 @@ SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) +
   FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
 
+-- Check invalid input for pg_stat_get_backend_io()
+SELECT pg_stat_get_backend_io(NULL);
+SELECT pg_stat_get_backend_io(0);
+-- Auxiliary processes return no data.
+SELECT pg_stat_get_backend_io(:checkpointer_pid);
 
 -- test BRIN index doesn't block HOT update
 CREATE TABLE brin_hot (
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4a9464da61..d0d176cc54 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -4801,11 +4801,13 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        <para>
         Returns I/O statistics about the backend with the specified
         process ID. The output fields are exactly the same as the ones in the
-        <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
-        view. The function does not return I/O statistics for the checkpointer,
+        <structname>pg_stat_io</structname> view.
+       </para>
+       <para>
+        The function does not return I/O statistics for the checkpointer,
         the background writer, the startup process and the autovacuum launcher
-        as they are already visible in the <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
-        view and there is only one of those.
+        as they are already visible in the <structname>pg_stat_io</structname>
+        view and there is only one of each.
        </para></entry>
       </row>
 
-- 
2.45.2



  [application/pgp-signature] signature.asc (833B, ../../[email protected]/4-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-17 08:13             ` Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-17 08:13 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Tue, Dec 17, 2024 at 03:26:02PM +0900, Michael Paquier wrote:
> On Mon, Dec 16, 2024 at 03:42:04PM +0000, Bertrand Drouvot wrote:
> >> Perhaps there's an argument for an entirely separate
> >> callback that would run before pgstat is plugged off, like a new
> >> before_shmem_exit() callback registered after the entry is created?
> > 
> > As the ProcKill() is run in shmem_exit() (and so after before_shmem_exit()),
> > I think that the way we currently drop the backend stats entry is fine (unless
> > I miss something about your concern).
> 
> Looking closely at this one, I think that you're right as long as the
> shutdown callback is registered before the entry is created.

Yeah, which is the case.

> Anyway, I have put my hands on v9.  A couple of notes, while hacking
> through it.  See v9-0002 for the parts I have modified, that applies
> on top of your v9-0001.

Thanks!

> You may have noticed fee2b3ea2ecdg, which led me to fix two comments
> as these paths are not related to only database objects.

Yeap ;-)

> Added more documentation, tweaked quite a bit the comments, a bit less
> the docs (no need for the two linkends) and applied an indentation.

Looks good to me.

> pg_stat_get_backend_io() can be simpler, and does not need the loop
> with the extra LocalPgBackendStatus.  It is possible to call once
> pgstat_get_beentry_by_proc_number() and retrieve the PID and the
> bktype for the two sanity checks we want to do on them.

Looked at the changes you've done and agree that's simpler.

> s/pgstat_fetch_proc_stat_io/pgstat_fetch_stat_backend/.

Not sure about this one as being called in pg_stat_get_backend_io().

> s/pgstat_create_backend_stat/pgstat_create_backend/.

LGTM.

> I've been wondering for quite a bit about PgStat_BackendPendingIO and
> PgStat_PendingIO, and concluded to define both in pgstat.h, with the
> former being defined based on the latter to keep the dependency
> between both at the same place.

That's fine by me.

Also:

=== 1

the changes in pgstat_flush_backend() make sense to me.

=== 2

- * Create the backend statistics entry for procnum.
+ * Create backend statistics entry for proc number.

Yeah, "proc number" looks better as already used in multiple places.

=== 3

the changes in stats.sql look good to me.

Having said that, v9-0002 looks good to me (except the pgstat_fetch_proc_stat_io
renaming).

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com





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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-17 09:13               ` Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Michael Paquier @ 2024-12-17 09:13 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Tue, Dec 17, 2024 at 08:13:59AM +0000, Bertrand Drouvot wrote:
> Having said that, v9-0002 looks good to me (except the pgstat_fetch_proc_stat_io
> renaming).

This routine returns a PgStat_Backend from a pgstats entry of type
PGSTAT_KIND_BACKEND, so the name in v9-0001 would not be true once
more types of stats data are attached to this structure.  The name in
v9-0002 is more consistent long-term.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-17 09:35                 ` Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-17 09:35 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Tue, Dec 17, 2024 at 06:13:44PM +0900, Michael Paquier wrote:
> On Tue, Dec 17, 2024 at 08:13:59AM +0000, Bertrand Drouvot wrote:
> > Having said that, v9-0002 looks good to me (except the pgstat_fetch_proc_stat_io
> > renaming).
> 
> This routine returns a PgStat_Backend from a pgstats entry of type
> PGSTAT_KIND_BACKEND, so the name in v9-0001 would not be true once
> more types of stats data are attached to this structure.

Fair enough, let's go with the name change done in v9-0002.

> The name in
> v9-0002 is more consistent long-term.

Agree, we may need to add parameters to it but we'll see when the time comes.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com





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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-18 04:57                   ` Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Michael Paquier @ 2024-12-18 04:57 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Tue, Dec 17, 2024 at 09:35:37AM +0000, Bertrand Drouvot wrote:
> Agree, we may need to add parameters to it but we'll see when the time comes.

Another thing that's been itching me: the loop in
pg_stat_get_backend_io() that exists as well in pg_stat_get_io() looks
worth refactoring in a single routine.  The only difference between
both is the reset timestamp, still both of them can pass a value for
it depending on their stats kind entry.  This shaves a bit more code
in your own patch, even if the check on pgstat_tracks_io_bktype() and
the Assert are not in the inner routine that fills the tuplestore with
the I/O data.  See pg_stat_fill_io_data() in v10-0002.  If you have a
better name for this routine, feel free..

What do you think about this refactoring?  This should come in first,
of course, so as the final patch introducing the backend stats is
easier to parse.
--
Michael


Attachments:

  [text/x-diff] v10-0001-per-backend-I-O-statistics.patch (36.5K, ../../[email protected]/2-v10-0001-per-backend-I-O-statistics.patch)
  download | inline diff:
From 75a2549543edf418b6e282acab3f8ce272c5c42a Mon Sep 17 00:00:00 2001
From: Bertrand Drouvot <[email protected]>
Date: Mon, 28 Oct 2024 12:50:32 +0000
Subject: [PATCH v10 1/2] per backend I/O statistics

While pg_stat_io provides cluster-wide I/O statistics, this commit adds the
ability to track and display per backend I/O statistics.

It adds a new statistics kind and 2 new functions:

- pg_stat_reset_backend_stats() to be able to reset the stats for a given
backend pid.
- pg_stat_get_backend_io() to retrieve I/O statistics for a given backend pid.

The new KIND is named PGSTAT_KIND_BACKEND as it could be used in the future
to store other statistics (than the I/O ones) per backend. The new KIND is
a variable-numbered one and has an automatic cap on the maximum number of
entries (as its hash key contains the proc number).

There is no need to write the per backend I/O stats to disk (no point to
see stats for backends that do not exist anymore after a re-start), so using
"write_to_file = false".

Note that per backend I/O statistics are not collected for the checkpointer,
the background writer, the startup process and the autovacuum launcher as those
are already visible in pg_stat_io and there is only one of those.

XXX: Bump catalog version needs to be done.
---
 src/include/catalog/pg_proc.dat              |  14 ++
 src/include/pgstat.h                         |  31 +++-
 src/include/utils/pgstat_internal.h          |  14 ++
 src/backend/catalog/system_functions.sql     |   2 +
 src/backend/utils/activity/Makefile          |   1 +
 src/backend/utils/activity/backend_status.c  |   4 +
 src/backend/utils/activity/meson.build       |   1 +
 src/backend/utils/activity/pgstat.c          |  21 +++
 src/backend/utils/activity/pgstat_backend.c  | 182 +++++++++++++++++++
 src/backend/utils/activity/pgstat_io.c       |  25 ++-
 src/backend/utils/activity/pgstat_relation.c |   2 +
 src/backend/utils/adt/pgstatfuncs.c          | 169 +++++++++++++++++
 src/test/regress/expected/stats.out          |  72 +++++++-
 src/test/regress/sql/stats.sql               |  38 +++-
 doc/src/sgml/config.sgml                     |   8 +-
 doc/src/sgml/monitoring.sgml                 |  37 ++++
 src/tools/pgindent/typedefs.list             |   3 +
 17 files changed, 604 insertions(+), 20 deletions(-)
 create mode 100644 src/backend/utils/activity/pgstat_backend.c

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..437157ffa3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5913,6 +5913,15 @@
   proargnames => '{backend_type,object,context,reads,read_time,writes,write_time,writebacks,writeback_time,extends,extend_time,op_bytes,hits,evictions,reuses,fsyncs,fsync_time,stats_reset}',
   prosrc => 'pg_stat_get_io' },
 
+{ oid => '8806', descr => 'statistics: backend IO statistics',
+  proname => 'pg_stat_get_backend_io', prorows => '5', proretset => 't',
+  provolatile => 'v', proparallel => 'r', prorettype => 'record',
+  proargtypes => 'int4',
+  proallargtypes => '{int4,text,text,text,int8,float8,int8,float8,int8,float8,int8,float8,int8,int8,int8,int8,int8,float8,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{backend_pid,backend_type,object,context,reads,read_time,writes,write_time,writebacks,writeback_time,extends,extend_time,op_bytes,hits,evictions,reuses,fsyncs,fsync_time,stats_reset}',
+  prosrc => 'pg_stat_get_backend_io' },
+
 { oid => '1136', descr => 'statistics: information about WAL activity',
   proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's',
   proparallel => 'r', prorettype => 'record', proargtypes => '',
@@ -6052,6 +6061,11 @@
   proname => 'pg_stat_reset_single_function_counters', provolatile => 'v',
   prorettype => 'void', proargtypes => 'oid',
   prosrc => 'pg_stat_reset_single_function_counters' },
+{ oid => '9987',
+  descr => 'statistics: reset statistics for a single backend',
+  proname => 'pg_stat_reset_backend_stats', provolatile => 'v',
+  prorettype => 'void', proargtypes => 'int4',
+  prosrc => 'pg_stat_reset_backend_stats' },
 { oid => '2307',
   descr => 'statistics: reset collected statistics for a single SLRU',
   proname => 'pg_stat_reset_slru', proisstrict => 'f', provolatile => 'v',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index ebfeef2f46..479773cfd2 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -49,14 +49,15 @@
 #define PGSTAT_KIND_FUNCTION	3	/* per-function statistics */
 #define PGSTAT_KIND_REPLSLOT	4	/* per-slot statistics */
 #define PGSTAT_KIND_SUBSCRIPTION	5	/* per-subscription statistics */
+#define PGSTAT_KIND_BACKEND	6	/* per-backend statistics */
 
 /* stats for fixed-numbered objects */
-#define PGSTAT_KIND_ARCHIVER	6
-#define PGSTAT_KIND_BGWRITER	7
-#define PGSTAT_KIND_CHECKPOINTER	8
-#define PGSTAT_KIND_IO	9
-#define PGSTAT_KIND_SLRU	10
-#define PGSTAT_KIND_WAL	11
+#define PGSTAT_KIND_ARCHIVER	7
+#define PGSTAT_KIND_BGWRITER	8
+#define PGSTAT_KIND_CHECKPOINTER	9
+#define PGSTAT_KIND_IO	10
+#define PGSTAT_KIND_SLRU	11
+#define PGSTAT_KIND_WAL	12
 
 #define PGSTAT_KIND_BUILTIN_MIN PGSTAT_KIND_DATABASE
 #define PGSTAT_KIND_BUILTIN_MAX PGSTAT_KIND_WAL
@@ -362,12 +363,23 @@ typedef struct PgStat_BktypeIO
 	PgStat_Counter times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 } PgStat_BktypeIO;
 
+typedef struct PgStat_BackendPendingIO
+{
+	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
+	instr_time	pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
+} PgStat_BackendPendingIO;
+
 typedef struct PgStat_IO
 {
 	TimestampTz stat_reset_timestamp;
 	PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
 } PgStat_IO;
 
+typedef struct PgStat_Backend
+{
+	TimestampTz stat_reset_timestamp;
+	PgStat_BktypeIO stats;
+} PgStat_Backend;
 
 typedef struct PgStat_StatDBEntry
 {
@@ -549,6 +561,13 @@ extern bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid);
 extern void pgstat_report_archiver(const char *xlog, bool failed);
 extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
 
+/*
+ * Functions in pgstat_backend.c
+ */
+
+extern PgStat_Backend *pgstat_fetch_proc_stat_io(ProcNumber procNumber);
+extern bool pgstat_tracks_backend_bktype(BackendType bktype);
+extern void pgstat_create_backend_stat(ProcNumber procnum);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 7338bc1e28..811ed9b005 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -450,6 +450,11 @@ typedef struct PgStatShared_ReplSlot
 	PgStat_StatReplSlotEntry stats;
 } PgStatShared_ReplSlot;
 
+typedef struct PgStatShared_Backend
+{
+	PgStatShared_Common header;
+	PgStat_Backend stats;
+} PgStatShared_Backend;
 
 /*
  * Central shared memory entry for the cumulative stats system.
@@ -604,6 +609,15 @@ extern void pgstat_archiver_init_shmem_cb(void *stats);
 extern void pgstat_archiver_reset_all_cb(TimestampTz ts);
 extern void pgstat_archiver_snapshot_cb(void);
 
+/*
+ * Functions in pgstat_backend.c
+ */
+
+extern void pgstat_flush_backend(bool nowait);
+
+extern PgStat_BackendPendingIO *pgstat_prep_backend_pending(ProcNumber procnum);
+extern bool pgstat_backend_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_backend_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index c51dfca802..14eb99cd47 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -711,6 +711,8 @@ REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_table_counters(oid) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM public;
 
+REVOKE EXECUTE ON FUNCTION pg_stat_reset_backend_stats(integer) FROM public;
+
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_replication_slot(text) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_have_stats(text, oid, int8) FROM public;
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index b9fd66ea17..24b64a2742 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -20,6 +20,7 @@ OBJS = \
 	backend_status.o \
 	pgstat.o \
 	pgstat_archiver.o \
+	pgstat_backend.o \
 	pgstat_bgwriter.o \
 	pgstat_checkpointer.o \
 	pgstat_database.o \
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index 22c6dc378c..e9c3b87ac2 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -426,6 +426,10 @@ pgstat_bestart(void)
 
 	PGSTAT_END_WRITE_ACTIVITY(vbeentry);
 
+	/* Create the backend statistics entry */
+	if (pgstat_tracks_backend_bktype(MyBackendType))
+		pgstat_create_backend_stat(MyProcNumber);
+
 	/* Update app name to current GUC setting */
 	if (application_name)
 		pgstat_report_appname(application_name);
diff --git a/src/backend/utils/activity/meson.build b/src/backend/utils/activity/meson.build
index f73c22905c..380d3dd70c 100644
--- a/src/backend/utils/activity/meson.build
+++ b/src/backend/utils/activity/meson.build
@@ -5,6 +5,7 @@ backend_sources += files(
   'backend_status.c',
   'pgstat.c',
   'pgstat_archiver.c',
+  'pgstat_backend.c',
   'pgstat_bgwriter.c',
   'pgstat_checkpointer.c',
   'pgstat_database.c',
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index b4e357c8a4..b72c779b2c 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -77,6 +77,7 @@
  *
  * Each statistics kind is handled in a dedicated file:
  * - pgstat_archiver.c
+ * - pgstat_backend.c
  * - pgstat_bgwriter.c
  * - pgstat_checkpointer.c
  * - pgstat_database.c
@@ -358,6 +359,22 @@ static const PgStat_KindInfo pgstat_kind_builtin_infos[PGSTAT_KIND_BUILTIN_SIZE]
 		.reset_timestamp_cb = pgstat_subscription_reset_timestamp_cb,
 	},
 
+	[PGSTAT_KIND_BACKEND] = {
+		.name = "backend",
+
+		.fixed_amount = false,
+		.write_to_file = false,
+
+		.accessed_across_databases = true,
+
+		.shared_size = sizeof(PgStatShared_Backend),
+		.shared_data_off = offsetof(PgStatShared_Backend, stats),
+		.shared_data_len = sizeof(((PgStatShared_Backend *) 0)->stats),
+		.pending_size = sizeof(PgStat_BackendPendingIO),
+
+		.flush_pending_cb = pgstat_backend_flush_cb,
+		.reset_timestamp_cb = pgstat_backend_reset_timestamp_cb,
+	},
 
 	/* stats for fixed-numbered (mostly 1) objects */
 
@@ -602,6 +619,10 @@ pgstat_shutdown_hook(int code, Datum arg)
 	Assert(dlist_is_empty(&pgStatPending));
 	dlist_init(&pgStatPending);
 
+	/* drop the backend stats entry */
+	if (!pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber))
+		pgstat_request_entry_refs_gc();
+
 	pgstat_detach_shmem();
 
 #ifdef USE_ASSERT_CHECKING
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
new file mode 100644
index 0000000000..e2d83024c2
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -0,0 +1,182 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_backend.c
+ *	  Implementation of backend statistics.
+ *
+ * This file contains the implementation of backend statistics. It is kept
+ * separate from pgstat.c to enforce the line between the statistics access /
+ * storage implementation and the details about individual types of statistics.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/activity/pgstat_backend.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "utils/pgstat_internal.h"
+
+/*
+ * Returns backend's IO stats.
+ */
+PgStat_Backend *
+pgstat_fetch_proc_stat_io(ProcNumber procNumber)
+{
+	PgStat_Backend *backend_entry;
+
+	backend_entry = (PgStat_Backend *) pgstat_fetch_entry(PGSTAT_KIND_BACKEND,
+														  InvalidOid, procNumber);
+
+	return backend_entry;
+}
+
+/*
+ * Flush out locally pending backend statistics
+ *
+ * If no stats have been recorded, this function returns false.
+ */
+bool
+pgstat_backend_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+	PgStatShared_Backend *shbackendioent;
+	PgStat_BackendPendingIO *pendingent;
+	PgStat_BktypeIO *bktype_shstats;
+
+	if (!pgstat_lock_entry(entry_ref, nowait))
+		return false;
+
+	shbackendioent = (PgStatShared_Backend *) entry_ref->shared_stats;
+	bktype_shstats = &shbackendioent->stats.stats;
+	pendingent = (PgStat_BackendPendingIO *) entry_ref->pending;
+
+	for (int io_object = 0; io_object < IOOBJECT_NUM_TYPES; io_object++)
+	{
+		for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+		{
+			for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+			{
+				instr_time	time;
+
+				bktype_shstats->counts[io_object][io_context][io_op] +=
+					pendingent->counts[io_object][io_context][io_op];
+
+				time = pendingent->pending_times[io_object][io_context][io_op];
+
+				bktype_shstats->times[io_object][io_context][io_op] +=
+					INSTR_TIME_GET_MICROSEC(time);
+			}
+		}
+	}
+
+	pgstat_unlock_entry(entry_ref);
+
+	return true;
+}
+
+/*
+ * Simpler wrapper of pgstat_backend_flush_cb()
+ */
+void
+pgstat_flush_backend(bool nowait)
+{
+	if (pgstat_tracks_backend_bktype(MyBackendType))
+	{
+		PgStat_EntryRef *entry_ref;
+
+		entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_BACKEND, InvalidOid,
+										 MyProcNumber, false, NULL);
+		(void) pgstat_backend_flush_cb(entry_ref, nowait);
+	}
+}
+
+/*
+ * Create the backend statistics entry for procnum.
+ */
+void
+pgstat_create_backend_stat(ProcNumber procnum)
+{
+	PgStat_EntryRef *entry_ref;
+	PgStatShared_Backend *shstatent;
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_BACKEND, InvalidOid,
+										  procnum, NULL);
+
+	shstatent = (PgStatShared_Backend *) entry_ref->shared_stats;
+
+	/*
+	 * NB: need to accept that there might be stats from an older backend,
+	 * e.g. if we previously used this proc number.
+	 */
+	memset(&shstatent->stats, 0, sizeof(shstatent->stats));
+}
+
+/*
+ * Find or create a local PgStat_BackendPendingIO entry for procnum.
+ */
+PgStat_BackendPendingIO *
+pgstat_prep_backend_pending(ProcNumber procnum)
+{
+	PgStat_EntryRef *entry_ref;
+
+	entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_BACKEND, InvalidOid,
+										  procnum, NULL);
+
+	return entry_ref->pending;
+}
+
+/*
+ * Backend statistics are not collected for all BackendTypes.
+ *
+ * The following BackendTypes do not participate in the backend stats
+ * subsystem:
+ * - The same and for the same reasons as in pgstat_tracks_io_bktype().
+ * - B_BG_WRITER, B_CHECKPOINTER, B_STARTUP and B_AUTOVAC_LAUNCHER because their
+ * I/O stats are already visible in pg_stat_io and there is only one of those.
+ *
+ * Function returns true if BackendType participates in the backend stats
+ * subsystem for IO and false if it does not.
+ *
+ * When adding a new BackendType, also consider adding relevant restrictions to
+ * pgstat_tracks_io_object() and pgstat_tracks_io_op().
+ */
+bool
+pgstat_tracks_backend_bktype(BackendType bktype)
+{
+	/*
+	 * List every type so that new backend types trigger a warning about
+	 * needing to adjust this switch.
+	 */
+	switch (bktype)
+	{
+		case B_INVALID:
+		case B_AUTOVAC_LAUNCHER:
+		case B_DEAD_END_BACKEND:
+		case B_ARCHIVER:
+		case B_LOGGER:
+		case B_WAL_RECEIVER:
+		case B_WAL_WRITER:
+		case B_WAL_SUMMARIZER:
+		case B_BG_WRITER:
+		case B_CHECKPOINTER:
+		case B_STARTUP:
+			return false;
+
+		case B_AUTOVAC_WORKER:
+		case B_BACKEND:
+		case B_BG_WORKER:
+		case B_STANDALONE_BACKEND:
+		case B_SLOTSYNC_WORKER:
+		case B_WAL_SENDER:
+			return true;
+	}
+
+	return false;
+}
+
+void
+pgstat_backend_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
+{
+	((PgStatShared_Backend *) header)->stats.stat_reset_timestamp = ts;
+}
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index f9883af2b3..e0c206a453 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -20,13 +20,7 @@
 #include "storage/bufmgr.h"
 #include "utils/pgstat_internal.h"
 
-
-typedef struct PgStat_PendingIO
-{
-	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-	instr_time	pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-} PgStat_PendingIO;
-
+typedef PgStat_BackendPendingIO PgStat_PendingIO;
 
 static PgStat_PendingIO PendingIOStats;
 static bool have_iostats = false;
@@ -87,6 +81,14 @@ pgstat_count_io_op_n(IOObject io_object, IOContext io_context, IOOp io_op, uint3
 	Assert((unsigned int) io_op < IOOP_NUM_TYPES);
 	Assert(pgstat_tracks_io_op(MyBackendType, io_object, io_context, io_op));
 
+	if (pgstat_tracks_backend_bktype(MyBackendType))
+	{
+		PgStat_PendingIO *entry_ref;
+
+		entry_ref = pgstat_prep_backend_pending(MyProcNumber);
+		entry_ref->counts[io_object][io_context][io_op] += cnt;
+	}
+
 	PendingIOStats.counts[io_object][io_context][io_op] += cnt;
 
 	have_iostats = true;
@@ -148,6 +150,15 @@ pgstat_count_io_op_time(IOObject io_object, IOContext io_context, IOOp io_op,
 
 		INSTR_TIME_ADD(PendingIOStats.pending_times[io_object][io_context][io_op],
 					   io_time);
+
+		if (pgstat_tracks_backend_bktype(MyBackendType))
+		{
+			PgStat_PendingIO *entry_ref;
+
+			entry_ref = pgstat_prep_backend_pending(MyProcNumber);
+			INSTR_TIME_ADD(entry_ref->pending_times[io_object][io_context][io_op],
+						   io_time);
+		}
 	}
 
 	pgstat_count_io_op_n(io_object, io_context, io_op, cnt);
diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c
index faba8b64d2..85e65557bb 100644
--- a/src/backend/utils/activity/pgstat_relation.c
+++ b/src/backend/utils/activity/pgstat_relation.c
@@ -264,6 +264,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared,
 	 * VACUUM command has processed all tables and committed.
 	 */
 	pgstat_flush_io(false);
+	pgstat_flush_backend(false);
 }
 
 /*
@@ -350,6 +351,7 @@ pgstat_report_analyze(Relation rel,
 
 	/* see pgstat_report_vacuum() */
 	pgstat_flush_io(false);
+	pgstat_flush_backend(false);
 }
 
 /*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index cdf37403e9..b939551d36 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1474,6 +1474,154 @@ pg_stat_get_io(PG_FUNCTION_ARGS)
 	return (Datum) 0;
 }
 
+Datum
+pg_stat_get_backend_io(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo;
+	PgStat_Backend *backend_stats;
+	Datum		bktype_desc;
+	PgStat_BktypeIO *bktype_stats;
+	BackendType bktype;
+	Datum		reset_time;
+	int			num_backends = pgstat_fetch_stat_numbackends();
+	int			curr_backend;
+	int			pid;
+	PGPROC	   *proc;
+	ProcNumber	procNumber;
+
+	InitMaterializedSRF(fcinfo, 0);
+	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	pid = PG_GETARG_INT32(0);
+	proc = BackendPidGetProc(pid);
+
+	/*
+	 * Could be an auxiliary process but would not report any stats due to
+	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
+	 * AuxiliaryPidGetProc().
+	 */
+	if (!proc)
+		return (Datum) 0;
+
+	procNumber = GetNumberFromPGProc(proc);
+	backend_stats = pgstat_fetch_proc_stat_io(procNumber);
+
+	if (!backend_stats)
+		return (Datum) 0;
+
+	bktype = B_INVALID;
+
+	/* Look for the backend type */
+	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
+	{
+		LocalPgBackendStatus *local_beentry;
+		PgBackendStatus *beentry;
+
+		/* Get the next one in the list */
+		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
+		beentry = &local_beentry->backendStatus;
+
+		/* Looking for specific PID, ignore all the others */
+		if (beentry->st_procpid != pid)
+			continue;
+
+		bktype = beentry->st_backendType;
+		break;
+	}
+
+	/* Backend is gone */
+	if (bktype == B_INVALID)
+		return (Datum) 0;
+
+	bktype_desc = CStringGetTextDatum(GetBackendTypeDesc(bktype));
+	bktype_stats = &backend_stats->stats;
+	reset_time = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
+
+	/*
+	 * In Assert builds, we can afford an extra loop through all of the
+	 * counters checking that only expected stats are non-zero, since it keeps
+	 * the non-Assert code cleaner.
+	 */
+	Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype));
+
+	for (int io_obj = 0; io_obj < IOOBJECT_NUM_TYPES; io_obj++)
+	{
+		const char *obj_name = pgstat_get_io_object_name(io_obj);
+
+		for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
+		{
+			const char *context_name = pgstat_get_io_context_name(io_context);
+
+			Datum		values[IO_NUM_COLUMNS] = {0};
+			bool		nulls[IO_NUM_COLUMNS] = {0};
+
+			/*
+			 * Some combinations of BackendType, IOObject, and IOContext are
+			 * not valid for any type of IOOp. In such cases, omit the entire
+			 * row from the view.
+			 */
+			if (!pgstat_tracks_io_object(bktype, io_obj, io_context))
+				continue;
+
+			values[IO_COL_BACKEND_TYPE] = bktype_desc;
+			values[IO_COL_CONTEXT] = CStringGetTextDatum(context_name);
+			values[IO_COL_OBJECT] = CStringGetTextDatum(obj_name);
+			if (backend_stats->stat_reset_timestamp != 0)
+				values[IO_COL_RESET_TIME] = reset_time;
+			else
+				nulls[IO_COL_RESET_TIME] = true;
+
+			/*
+			 * Hard-code this to the value of BLCKSZ for now. Future values
+			 * could include XLOG_BLCKSZ, once WAL IO is tracked, and constant
+			 * multipliers, once non-block-oriented IO (e.g. temporary file
+			 * IO) is tracked.
+			 */
+			values[IO_COL_CONVERSION] = Int64GetDatum(BLCKSZ);
+
+			for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
+			{
+				int			op_idx = pgstat_get_io_op_index(io_op);
+				int			time_idx = pgstat_get_io_time_index(io_op);
+
+				/*
+				 * Some combinations of BackendType and IOOp, of IOContext and
+				 * IOOp, and of IOObject and IOOp are not tracked. Set these
+				 * cells in the view NULL.
+				 */
+				if (pgstat_tracks_io_op(bktype, io_obj, io_context, io_op))
+				{
+					PgStat_Counter count =
+						bktype_stats->counts[io_obj][io_context][io_op];
+
+					values[op_idx] = Int64GetDatum(count);
+				}
+				else
+					nulls[op_idx] = true;
+
+				/* not every operation is timed */
+				if (time_idx == IO_COL_INVALID)
+					continue;
+
+				if (!nulls[op_idx])
+				{
+					PgStat_Counter time =
+						bktype_stats->times[io_obj][io_context][io_op];
+
+					values[time_idx] = Float8GetDatum(pg_stat_us_to_ms(time));
+				}
+				else
+					nulls[time_idx] = true;
+			}
+
+			tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
+								 values, nulls);
+		}
+	}
+
+	return (Datum) 0;
+}
+
 /*
  * Returns statistics of WAL activity
  */
@@ -1779,6 +1927,27 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+Datum
+pg_stat_reset_backend_stats(PG_FUNCTION_ARGS)
+{
+	PGPROC	   *proc;
+	int			backend_pid = PG_GETARG_INT32(0);
+
+	proc = BackendPidGetProc(backend_pid);
+
+	/*
+	 * Could be an auxiliary process but would not report any stats due to
+	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
+	 * AuxiliaryPidGetProc().
+	 */
+	if (!proc)
+		PG_RETURN_VOID();
+
+	pgstat_reset(PGSTAT_KIND_BACKEND, InvalidOid, GetNumberFromPGProc(proc));
+
+	PG_RETURN_VOID();
+}
+
 /* Reset SLRU counters (a specific one or all of them). */
 Datum
 pg_stat_reset_slru(PG_FUNCTION_ARGS)
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 56771f83ed..3447e7b75d 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1249,7 +1249,7 @@ SELECT pg_stat_get_subscription_stats(NULL);
  
 (1 row)
 
--- Test that the following operations are tracked in pg_stat_io:
+-- Test that the following operations are tracked in pg_stat_io and in backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -1259,11 +1259,19 @@ SELECT pg_stat_get_subscription_stats(NULL);
 -- be sure of the state of shared buffers at the point the test is run.
 -- Create a regular table and insert some data to generate IOCONTEXT_NORMAL
 -- extends.
+SELECT pid AS checkpointer_pid FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer' \gset
 SELECT sum(extends) AS io_sum_shared_before_extends
   FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
+SELECT sum(extends) AS my_io_sum_shared_before_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
 SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
   FROM pg_stat_io
   WHERE object = 'relation' \gset io_sum_shared_before_
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_before_
 CREATE TABLE test_io_shared(a int);
 INSERT INTO test_io_shared SELECT i FROM generate_series(1,100)i;
 SELECT pg_stat_force_next_flush();
@@ -1280,8 +1288,17 @@ SELECT :io_sum_shared_after_extends > :io_sum_shared_before_extends;
  t
 (1 row)
 
+SELECT sum(extends) AS my_io_sum_shared_after_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
+SELECT :my_io_sum_shared_after_extends > :my_io_sum_shared_before_extends;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- After a checkpoint, there should be some additional IOCONTEXT_NORMAL writes
--- and fsyncs.
+-- and fsyncs in the global stats (not for the backend).
 -- See comment above for rationale for two explicit CHECKPOINTs.
 CHECKPOINT;
 CHECKPOINT;
@@ -1301,6 +1318,31 @@ SELECT current_setting('fsync') = 'off'
  t
 (1 row)
 
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_after_
+SELECT :my_io_sum_shared_after_writes >= :my_io_sum_shared_before_writes;
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT current_setting('fsync') = 'off'
+  OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
+      AND :my_io_sum_shared_after_fsyncs= 0);
+ ?column? 
+----------
+ t
+(1 row)
+
+-- Don't return any rows if querying other backend's stats that are excluded
+-- from the backend stats collection (like the checkpointer).
+SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
+ ?column? 
+----------
+ t
+(1 row)
+
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
 SELECT sum(reads) AS io_sum_shared_before_reads
@@ -1521,6 +1563,8 @@ SELECT pg_stat_have_stats('io', 0, 0);
 
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset
   FROM pg_stat_io \gset
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_pre_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT pg_stat_reset_shared('io');
  pg_stat_reset_shared 
 ----------------------
@@ -1535,6 +1579,30 @@ SELECT :io_stats_post_reset < :io_stats_pre_reset;
  t
 (1 row)
 
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+-- pg_stat_reset_shared() did not reset backend IO stats
+SELECT :my_io_stats_pre_reset <= :my_io_stats_post_reset;
+ ?column? 
+----------
+ t
+(1 row)
+
+-- but pg_stat_reset_backend_stats() does
+SELECT pg_stat_reset_backend_stats(pg_backend_pid());
+ pg_stat_reset_backend_stats 
+-----------------------------
+ 
+(1 row)
+
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_backend_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
+ ?column? 
+----------
+ t
+(1 row)
+
 -- test BRIN index doesn't block HOT update
 CREATE TABLE brin_hot (
   id  integer PRIMARY KEY,
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 7147cc2f89..9c925005be 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -595,7 +595,7 @@ SELECT pg_stat_get_replication_slot(NULL);
 SELECT pg_stat_get_subscription_stats(NULL);
 
 
--- Test that the following operations are tracked in pg_stat_io:
+-- Test that the following operations are tracked in pg_stat_io and in backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -607,20 +607,32 @@ SELECT pg_stat_get_subscription_stats(NULL);
 
 -- Create a regular table and insert some data to generate IOCONTEXT_NORMAL
 -- extends.
+SELECT pid AS checkpointer_pid FROM pg_stat_activity
+  WHERE backend_type = 'checkpointer' \gset
 SELECT sum(extends) AS io_sum_shared_before_extends
   FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
+SELECT sum(extends) AS my_io_sum_shared_before_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
 SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
   FROM pg_stat_io
   WHERE object = 'relation' \gset io_sum_shared_before_
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_before_
 CREATE TABLE test_io_shared(a int);
 INSERT INTO test_io_shared SELECT i FROM generate_series(1,100)i;
 SELECT pg_stat_force_next_flush();
 SELECT sum(extends) AS io_sum_shared_after_extends
   FROM pg_stat_io WHERE context = 'normal' AND object = 'relation' \gset
 SELECT :io_sum_shared_after_extends > :io_sum_shared_before_extends;
+SELECT sum(extends) AS my_io_sum_shared_after_extends
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE context = 'normal' AND object = 'relation' \gset
+SELECT :my_io_sum_shared_after_extends > :my_io_sum_shared_before_extends;
 
 -- After a checkpoint, there should be some additional IOCONTEXT_NORMAL writes
--- and fsyncs.
+-- and fsyncs in the global stats (not for the backend).
 -- See comment above for rationale for two explicit CHECKPOINTs.
 CHECKPOINT;
 CHECKPOINT;
@@ -630,6 +642,17 @@ SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
 SELECT :io_sum_shared_after_writes > :io_sum_shared_before_writes;
 SELECT current_setting('fsync') = 'off'
   OR :io_sum_shared_after_fsyncs > :io_sum_shared_before_fsyncs;
+SELECT sum(writes) AS writes, sum(fsyncs) AS fsyncs
+  FROM pg_stat_get_backend_io(pg_backend_pid())
+  WHERE object = 'relation' \gset my_io_sum_shared_after_
+SELECT :my_io_sum_shared_after_writes >= :my_io_sum_shared_before_writes;
+SELECT current_setting('fsync') = 'off'
+  OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
+      AND :my_io_sum_shared_after_fsyncs= 0);
+
+-- Don't return any rows if querying other backend's stats that are excluded
+-- from the backend stats collection (like the checkpointer).
+SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
 
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
@@ -762,10 +785,21 @@ SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_ext
 SELECT pg_stat_have_stats('io', 0, 0);
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset
   FROM pg_stat_io \gset
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_pre_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT pg_stat_reset_shared('io');
 SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_post_reset
   FROM pg_stat_io \gset
 SELECT :io_stats_post_reset < :io_stats_pre_reset;
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+-- pg_stat_reset_shared() did not reset backend IO stats
+SELECT :my_io_stats_pre_reset <= :my_io_stats_post_reset;
+-- but pg_stat_reset_backend_stats() does
+SELECT pg_stat_reset_backend_stats(pg_backend_pid());
+SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS my_io_stats_post_backend_reset
+  FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
+SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
 
 
 -- test BRIN index doesn't block HOT update
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 24bd504c21..fbdd6ce574 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8403,9 +8403,11 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
         displayed in <link linkend="monitoring-pg-stat-database-view">
         <structname>pg_stat_database</structname></link>,
         <link linkend="monitoring-pg-stat-io-view">
-        <structname>pg_stat_io</structname></link>, in the output of
-        <xref linkend="sql-explain"/> when the <literal>BUFFERS</literal> option
-        is used, in the output of <xref linkend="sql-vacuum"/> when
+        <structname>pg_stat_io</structname></link>, in the output of the
+        <link linkend="pg-stat-get-backend-io">
+        <function>pg_stat_get_backend_io()</function></link> function, in the
+        output of <xref linkend="sql-explain"/> when the <literal>BUFFERS</literal>
+        option is used, in the output of <xref linkend="sql-vacuum"/> when
         the <literal>VERBOSE</literal> option is used, by autovacuum
         for auto-vacuums and auto-analyzes, when <xref
         linkend="guc-log-autovacuum-min-duration"/> is set and by
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 840d7f8161..4a9464da61 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -4790,6 +4790,25 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry id="pg-stat-get-backend-io" role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_get_backend_io</primary>
+        </indexterm>
+        <function>pg_stat_get_backend_io</function> ( <type>integer</type> )
+        <returnvalue>setof record</returnvalue>
+       </para>
+       <para>
+        Returns I/O statistics about the backend with the specified
+        process ID. The output fields are exactly the same as the ones in the
+        <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
+        view. The function does not return I/O statistics for the checkpointer,
+        the background writer, the startup process and the autovacuum launcher
+        as they are already visible in the <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
+        view and there is only one of those.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
@@ -4971,6 +4990,24 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_stat_reset_backend_stats</primary>
+        </indexterm>
+        <function>pg_stat_reset_backend_stats</function> ( <type>integer</type> )
+        <returnvalue>void</returnvalue>
+       </para>
+       <para>
+        Resets statistics for a single backend with the specified process ID
+        to zero.
+       </para>
+       <para>
+        This function is restricted to superusers by default, but other users
+        can be granted EXECUTE to run the function.
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index ce33e55bf1..398dd92527 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2121,6 +2121,7 @@ PgFdwSamplingMethod
 PgFdwScanState
 PgIfAddrCallback
 PgStatShared_Archiver
+PgStatShared_Backend
 PgStatShared_BgWriter
 PgStatShared_Checkpointer
 PgStatShared_Common
@@ -2136,6 +2137,8 @@ PgStatShared_SLRU
 PgStatShared_Subscription
 PgStatShared_Wal
 PgStat_ArchiverStats
+PgStat_Backend
+PgStat_BackendPendingIO
 PgStat_BackendSubEntry
 PgStat_BgWriterStats
 PgStat_BktypeIO
-- 
2.45.2



  [text/x-diff] v10-0002-Tweaks-on-top-of-v9.patch (21.6K, ../../[email protected]/3-v10-0002-Tweaks-on-top-of-v9.patch)
  download | inline diff:
From 0e3c2c066698acaaad69824a619e78f2cc16a358 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Wed, 18 Dec 2024 13:48:34 +0900
Subject: [PATCH v10 2/2] Tweaks on top of v9

Other notes for commit:
Requires a bump of CATALOG_VERSION_NO.
Requires a bump of PGSTAT_FILE_FORMAT_ID.
---
 src/include/catalog/pg_proc.dat             |   3 +-
 src/include/pgstat.h                        |  11 +-
 src/backend/utils/activity/backend_status.c |   2 +-
 src/backend/utils/activity/pgstat_backend.c |  38 ++-
 src/backend/utils/activity/pgstat_io.c      |   2 -
 src/backend/utils/adt/pgstatfuncs.c         | 305 ++++++++------------
 src/test/regress/expected/stats.out         |  28 +-
 src/test/regress/sql/stats.sql              |  12 +-
 doc/src/sgml/monitoring.sgml                |  10 +-
 9 files changed, 184 insertions(+), 227 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 437157ffa3..2dcc2d42da 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6061,8 +6061,7 @@
   proname => 'pg_stat_reset_single_function_counters', provolatile => 'v',
   prorettype => 'void', proargtypes => 'oid',
   prosrc => 'pg_stat_reset_single_function_counters' },
-{ oid => '9987',
-  descr => 'statistics: reset statistics for a single backend',
+{ oid => '8807', descr => 'statistics: reset statistics for a single backend',
   proname => 'pg_stat_reset_backend_stats', provolatile => 'v',
   prorettype => 'void', proargtypes => 'int4',
   prosrc => 'pg_stat_reset_backend_stats' },
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 479773cfd2..56ed1b8bb3 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -363,11 +363,11 @@ typedef struct PgStat_BktypeIO
 	PgStat_Counter times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 } PgStat_BktypeIO;
 
-typedef struct PgStat_BackendPendingIO
+typedef struct PgStat_PendingIO
 {
 	PgStat_Counter counts[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
 	instr_time	pending_times[IOOBJECT_NUM_TYPES][IOCONTEXT_NUM_TYPES][IOOP_NUM_TYPES];
-} PgStat_BackendPendingIO;
+} PgStat_PendingIO;
 
 typedef struct PgStat_IO
 {
@@ -375,6 +375,9 @@ typedef struct PgStat_IO
 	PgStat_BktypeIO stats[BACKEND_NUM_TYPES];
 } PgStat_IO;
 
+/* Backend statistics store the same amount of IO data as PGSTAT_KIND_IO */
+typedef PgStat_PendingIO PgStat_BackendPendingIO;
+
 typedef struct PgStat_Backend
 {
 	TimestampTz stat_reset_timestamp;
@@ -565,9 +568,9 @@ extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
  * Functions in pgstat_backend.c
  */
 
-extern PgStat_Backend *pgstat_fetch_proc_stat_io(ProcNumber procNumber);
+extern PgStat_Backend *pgstat_fetch_stat_backend(ProcNumber procNumber);
 extern bool pgstat_tracks_backend_bktype(BackendType bktype);
-extern void pgstat_create_backend_stat(ProcNumber procnum);
+extern void pgstat_create_backend(ProcNumber procnum);
 
 /*
  * Functions in pgstat_bgwriter.c
diff --git a/src/backend/utils/activity/backend_status.c b/src/backend/utils/activity/backend_status.c
index e9c3b87ac2..bf33e33a4e 100644
--- a/src/backend/utils/activity/backend_status.c
+++ b/src/backend/utils/activity/backend_status.c
@@ -428,7 +428,7 @@ pgstat_bestart(void)
 
 	/* Create the backend statistics entry */
 	if (pgstat_tracks_backend_bktype(MyBackendType))
-		pgstat_create_backend_stat(MyProcNumber);
+		pgstat_create_backend(MyProcNumber);
 
 	/* Update app name to current GUC setting */
 	if (application_name)
diff --git a/src/backend/utils/activity/pgstat_backend.c b/src/backend/utils/activity/pgstat_backend.c
index e2d83024c2..6b2c9baa8c 100644
--- a/src/backend/utils/activity/pgstat_backend.c
+++ b/src/backend/utils/activity/pgstat_backend.c
@@ -3,11 +3,17 @@
  * pgstat_backend.c
  *	  Implementation of backend statistics.
  *
- * This file contains the implementation of backend statistics. It is kept
+ * This file contains the implementation of backend statistics.  It is kept
  * separate from pgstat.c to enforce the line between the statistics access /
- * storage implementation and the details about individual types of statistics.
+ * storage implementation and the details about individual types of
+ * statistics.
  *
- * Copyright (c) 2024, PostgreSQL Global Development Group
+ * This statistics kind uses a proc number as object ID for the hash table
+ * of pgstats.  Entries are created each time a process is spawned, and are
+ * dropped when the process exits.  These are not written to the pgstats file
+ * on disk.
+ *
+ * Copyright (c) 2001-2024, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
  *	  src/backend/utils/activity/pgstat_backend.c
@@ -19,10 +25,10 @@
 #include "utils/pgstat_internal.h"
 
 /*
- * Returns backend's IO stats.
+ * Returns statistics of a backend by proc number.
  */
 PgStat_Backend *
-pgstat_fetch_proc_stat_io(ProcNumber procNumber)
+pgstat_fetch_stat_backend(ProcNumber procNumber)
 {
 	PgStat_Backend *backend_entry;
 
@@ -81,21 +87,21 @@ pgstat_backend_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
 void
 pgstat_flush_backend(bool nowait)
 {
-	if (pgstat_tracks_backend_bktype(MyBackendType))
-	{
-		PgStat_EntryRef *entry_ref;
+	PgStat_EntryRef *entry_ref;
 
-		entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_BACKEND, InvalidOid,
-										 MyProcNumber, false, NULL);
-		(void) pgstat_backend_flush_cb(entry_ref, nowait);
-	}
+	if (!pgstat_tracks_backend_bktype(MyBackendType))
+		return;
+
+	entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_BACKEND, InvalidOid,
+									 MyProcNumber, false, NULL);
+	(void) pgstat_backend_flush_cb(entry_ref, nowait);
 }
 
 /*
- * Create the backend statistics entry for procnum.
+ * Create backend statistics entry for proc number.
  */
 void
-pgstat_create_backend_stat(ProcNumber procnum)
+pgstat_create_backend(ProcNumber procnum)
 {
 	PgStat_EntryRef *entry_ref;
 	PgStatShared_Backend *shstatent;
@@ -113,7 +119,7 @@ pgstat_create_backend_stat(ProcNumber procnum)
 }
 
 /*
- * Find or create a local PgStat_BackendPendingIO entry for procnum.
+ * Find or create a local PgStat_BackendPendingIO entry for proc number.
  */
 PgStat_BackendPendingIO *
 pgstat_prep_backend_pending(ProcNumber procnum)
@@ -136,7 +142,7 @@ pgstat_prep_backend_pending(ProcNumber procnum)
  * I/O stats are already visible in pg_stat_io and there is only one of those.
  *
  * Function returns true if BackendType participates in the backend stats
- * subsystem for IO and false if it does not.
+ * subsystem and false if it does not.
  *
  * When adding a new BackendType, also consider adding relevant restrictions to
  * pgstat_tracks_io_object() and pgstat_tracks_io_op().
diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c
index e0c206a453..011a3326da 100644
--- a/src/backend/utils/activity/pgstat_io.c
+++ b/src/backend/utils/activity/pgstat_io.c
@@ -20,8 +20,6 @@
 #include "storage/bufmgr.h"
 #include "utils/pgstat_internal.h"
 
-typedef PgStat_BackendPendingIO PgStat_PendingIO;
-
 static PgStat_PendingIO PendingIOStats;
 static bool have_iostats = false;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index b939551d36..18c74807a4 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1274,8 +1274,9 @@ pg_stat_get_buf_alloc(PG_FUNCTION_ARGS)
 }
 
 /*
-* When adding a new column to the pg_stat_io view, add a new enum value
-* here above IO_NUM_COLUMNS.
+* When adding a new column to the pg_stat_io view and the
+* pg_stat_get_backend_io() function, add a new enum value here above
+* IO_NUM_COLUMNS.
 */
 typedef enum io_stat_col
 {
@@ -1365,184 +1366,20 @@ pg_stat_us_to_ms(PgStat_Counter val_ms)
 	return val_ms * (double) 0.001;
 }
 
-Datum
-pg_stat_get_io(PG_FUNCTION_ARGS)
+/*
+ * pg_stat_fill_io_data
+ *
+ * Helper routine for pg_stat_get_io() and pg_stat_get_backend_io(),
+ * filling in a result tuplestore with one tuple for each object and each
+ * context supported by the caller, based on the contents of bktype_stats.
+ */
+static void
+pg_stat_fill_io_data(ReturnSetInfo *rsinfo,
+					 PgStat_BktypeIO *bktype_stats,
+					 BackendType bktype,
+					 TimestampTz stat_reset_timestamp)
 {
-	ReturnSetInfo *rsinfo;
-	PgStat_IO  *backends_io_stats;
-	Datum		reset_time;
-
-	InitMaterializedSRF(fcinfo, 0);
-	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-
-	backends_io_stats = pgstat_fetch_stat_io();
-
-	reset_time = TimestampTzGetDatum(backends_io_stats->stat_reset_timestamp);
-
-	for (int bktype = 0; bktype < BACKEND_NUM_TYPES; bktype++)
-	{
-		Datum		bktype_desc = CStringGetTextDatum(GetBackendTypeDesc(bktype));
-		PgStat_BktypeIO *bktype_stats = &backends_io_stats->stats[bktype];
-
-		/*
-		 * In Assert builds, we can afford an extra loop through all of the
-		 * counters checking that only expected stats are non-zero, since it
-		 * keeps the non-Assert code cleaner.
-		 */
-		Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype));
-
-		/*
-		 * For those BackendTypes without IO Operation stats, skip
-		 * representing them in the view altogether.
-		 */
-		if (!pgstat_tracks_io_bktype(bktype))
-			continue;
-
-		for (int io_obj = 0; io_obj < IOOBJECT_NUM_TYPES; io_obj++)
-		{
-			const char *obj_name = pgstat_get_io_object_name(io_obj);
-
-			for (int io_context = 0; io_context < IOCONTEXT_NUM_TYPES; io_context++)
-			{
-				const char *context_name = pgstat_get_io_context_name(io_context);
-
-				Datum		values[IO_NUM_COLUMNS] = {0};
-				bool		nulls[IO_NUM_COLUMNS] = {0};
-
-				/*
-				 * Some combinations of BackendType, IOObject, and IOContext
-				 * are not valid for any type of IOOp. In such cases, omit the
-				 * entire row from the view.
-				 */
-				if (!pgstat_tracks_io_object(bktype, io_obj, io_context))
-					continue;
-
-				values[IO_COL_BACKEND_TYPE] = bktype_desc;
-				values[IO_COL_CONTEXT] = CStringGetTextDatum(context_name);
-				values[IO_COL_OBJECT] = CStringGetTextDatum(obj_name);
-				values[IO_COL_RESET_TIME] = reset_time;
-
-				/*
-				 * Hard-code this to the value of BLCKSZ for now. Future
-				 * values could include XLOG_BLCKSZ, once WAL IO is tracked,
-				 * and constant multipliers, once non-block-oriented IO (e.g.
-				 * temporary file IO) is tracked.
-				 */
-				values[IO_COL_CONVERSION] = Int64GetDatum(BLCKSZ);
-
-				for (int io_op = 0; io_op < IOOP_NUM_TYPES; io_op++)
-				{
-					int			op_idx = pgstat_get_io_op_index(io_op);
-					int			time_idx = pgstat_get_io_time_index(io_op);
-
-					/*
-					 * Some combinations of BackendType and IOOp, of IOContext
-					 * and IOOp, and of IOObject and IOOp are not tracked. Set
-					 * these cells in the view NULL.
-					 */
-					if (pgstat_tracks_io_op(bktype, io_obj, io_context, io_op))
-					{
-						PgStat_Counter count =
-							bktype_stats->counts[io_obj][io_context][io_op];
-
-						values[op_idx] = Int64GetDatum(count);
-					}
-					else
-						nulls[op_idx] = true;
-
-					/* not every operation is timed */
-					if (time_idx == IO_COL_INVALID)
-						continue;
-
-					if (!nulls[op_idx])
-					{
-						PgStat_Counter time =
-							bktype_stats->times[io_obj][io_context][io_op];
-
-						values[time_idx] = Float8GetDatum(pg_stat_us_to_ms(time));
-					}
-					else
-						nulls[time_idx] = true;
-				}
-
-				tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
-									 values, nulls);
-			}
-		}
-	}
-
-	return (Datum) 0;
-}
-
-Datum
-pg_stat_get_backend_io(PG_FUNCTION_ARGS)
-{
-	ReturnSetInfo *rsinfo;
-	PgStat_Backend *backend_stats;
-	Datum		bktype_desc;
-	PgStat_BktypeIO *bktype_stats;
-	BackendType bktype;
-	Datum		reset_time;
-	int			num_backends = pgstat_fetch_stat_numbackends();
-	int			curr_backend;
-	int			pid;
-	PGPROC	   *proc;
-	ProcNumber	procNumber;
-
-	InitMaterializedSRF(fcinfo, 0);
-	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
-
-	pid = PG_GETARG_INT32(0);
-	proc = BackendPidGetProc(pid);
-
-	/*
-	 * Could be an auxiliary process but would not report any stats due to
-	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
-	 * AuxiliaryPidGetProc().
-	 */
-	if (!proc)
-		return (Datum) 0;
-
-	procNumber = GetNumberFromPGProc(proc);
-	backend_stats = pgstat_fetch_proc_stat_io(procNumber);
-
-	if (!backend_stats)
-		return (Datum) 0;
-
-	bktype = B_INVALID;
-
-	/* Look for the backend type */
-	for (curr_backend = 1; curr_backend <= num_backends; curr_backend++)
-	{
-		LocalPgBackendStatus *local_beentry;
-		PgBackendStatus *beentry;
-
-		/* Get the next one in the list */
-		local_beentry = pgstat_get_local_beentry_by_index(curr_backend);
-		beentry = &local_beentry->backendStatus;
-
-		/* Looking for specific PID, ignore all the others */
-		if (beentry->st_procpid != pid)
-			continue;
-
-		bktype = beentry->st_backendType;
-		break;
-	}
-
-	/* Backend is gone */
-	if (bktype == B_INVALID)
-		return (Datum) 0;
-
-	bktype_desc = CStringGetTextDatum(GetBackendTypeDesc(bktype));
-	bktype_stats = &backend_stats->stats;
-	reset_time = TimestampTzGetDatum(backend_stats->stat_reset_timestamp);
-
-	/*
-	 * In Assert builds, we can afford an extra loop through all of the
-	 * counters checking that only expected stats are non-zero, since it keeps
-	 * the non-Assert code cleaner.
-	 */
-	Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype));
+	Datum		bktype_desc = CStringGetTextDatum(GetBackendTypeDesc(bktype));
 
 	for (int io_obj = 0; io_obj < IOOBJECT_NUM_TYPES; io_obj++)
 	{
@@ -1566,8 +1403,8 @@ pg_stat_get_backend_io(PG_FUNCTION_ARGS)
 			values[IO_COL_BACKEND_TYPE] = bktype_desc;
 			values[IO_COL_CONTEXT] = CStringGetTextDatum(context_name);
 			values[IO_COL_OBJECT] = CStringGetTextDatum(obj_name);
-			if (backend_stats->stat_reset_timestamp != 0)
-				values[IO_COL_RESET_TIME] = reset_time;
+			if (stat_reset_timestamp != 0)
+				values[IO_COL_RESET_TIME] = TimestampTzGetDatum(stat_reset_timestamp);
 			else
 				nulls[IO_COL_RESET_TIME] = true;
 
@@ -1618,7 +1455,104 @@ pg_stat_get_backend_io(PG_FUNCTION_ARGS)
 								 values, nulls);
 		}
 	}
+}
 
+Datum
+pg_stat_get_io(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo;
+	PgStat_IO  *backends_io_stats;
+
+	InitMaterializedSRF(fcinfo, 0);
+	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	backends_io_stats = pgstat_fetch_stat_io();
+
+	for (int bktype = 0; bktype < BACKEND_NUM_TYPES; bktype++)
+	{
+		PgStat_BktypeIO *bktype_stats = &backends_io_stats->stats[bktype];
+
+		/*
+		 * In Assert builds, we can afford an extra loop through all of the
+		 * counters checking that only expected stats are non-zero, since it
+		 * keeps the non-Assert code cleaner.
+		 */
+		Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype));
+
+		/*
+		 * For those BackendTypes without IO Operation stats, skip
+		 * representing them in the view altogether.
+		 */
+		if (!pgstat_tracks_io_bktype(bktype))
+			continue;
+
+		/* Save tuples with data from this PgStat_BktypeIO. */
+		pg_stat_fill_io_data(rsinfo, bktype_stats, bktype,
+							 backends_io_stats->stat_reset_timestamp);
+	}
+
+	return (Datum) 0;
+}
+
+/*
+ * Returns I/O statistics for a backend with given PID.
+ */
+Datum
+pg_stat_get_backend_io(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo *rsinfo;
+	BackendType bktype;
+	int			pid;
+	PGPROC	   *proc;
+	ProcNumber	procNumber;
+	PgStat_Backend *backend_stats;
+	PgStat_BktypeIO *bktype_stats;
+	PgBackendStatus *beentry;
+
+	InitMaterializedSRF(fcinfo, 0);
+	rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	pid = PG_GETARG_INT32(0);
+	proc = BackendPidGetProc(pid);
+
+	/*
+	 * This could be an auxiliary process but these do not report backend
+	 * statistics due to pgstat_tracks_backend_bktype(), so there is no need
+	 * for an extra call to AuxiliaryPidGetProc().
+	 */
+	if (!proc)
+		return (Datum) 0;
+
+	procNumber = GetNumberFromPGProc(proc);
+
+	backend_stats = pgstat_fetch_stat_backend(procNumber);
+
+	if (!backend_stats)
+		return (Datum) 0;
+
+	beentry = pgstat_get_beentry_by_proc_number(procNumber);
+	bktype = beentry->st_backendType;
+
+	/* If PID does not match, leave */
+	if (beentry->st_procpid != pid)
+		return (Datum) 0;
+
+	/* Backend may be gone, so recheck in case */
+	if (bktype == B_INVALID)
+		return (Datum) 0;
+
+	bktype_stats = &backend_stats->stats;
+
+	/*
+	 * In Assert builds, we can afford an extra loop through all of the
+	 * counters checking that only expected stats are non-zero, since it keeps
+	 * the non-Assert code cleaner.
+	 */
+	Assert(pgstat_bktype_io_stats_valid(bktype_stats, bktype));
+
+	/* Save tuples with data from this PgStat_BktypeIO */
+	pg_stat_fill_io_data(rsinfo, bktype_stats, bktype,
+						 backend_stats->stat_reset_timestamp);
 	return (Datum) 0;
 }
 
@@ -1927,6 +1861,9 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * Reset statistics of backend with given PID.
+ */
 Datum
 pg_stat_reset_backend_stats(PG_FUNCTION_ARGS)
 {
@@ -1936,9 +1873,9 @@ pg_stat_reset_backend_stats(PG_FUNCTION_ARGS)
 	proc = BackendPidGetProc(backend_pid);
 
 	/*
-	 * Could be an auxiliary process but would not report any stats due to
-	 * pgstat_tracks_backend_bktype() anyway. So don't need an extra call to
-	 * AuxiliaryPidGetProc().
+	 * This could be an auxiliary process but these do not report backend
+	 * statistics due to pgstat_tracks_backend_bktype(), so there is no need
+	 * for an extra call to AuxiliaryPidGetProc().
 	 */
 	if (!proc)
 		PG_RETURN_VOID();
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 3447e7b75d..150b6dcf74 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -1249,7 +1249,8 @@ SELECT pg_stat_get_subscription_stats(NULL);
  
 (1 row)
 
--- Test that the following operations are tracked in pg_stat_io and in backend stats:
+-- Test that the following operations are tracked in pg_stat_io and in
+-- backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -1335,14 +1336,6 @@ SELECT current_setting('fsync') = 'off'
  t
 (1 row)
 
--- Don't return any rows if querying other backend's stats that are excluded
--- from the backend stats collection (like the checkpointer).
-SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
- ?column? 
-----------
- t
-(1 row)
-
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
 SELECT sum(reads) AS io_sum_shared_before_reads
@@ -1603,6 +1596,23 @@ SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
  t
 (1 row)
 
+-- Check invalid input for pg_stat_get_backend_io()
+SELECT pg_stat_get_backend_io(NULL);
+ pg_stat_get_backend_io 
+------------------------
+(0 rows)
+
+SELECT pg_stat_get_backend_io(0);
+ pg_stat_get_backend_io 
+------------------------
+(0 rows)
+
+-- Auxiliary processes return no data.
+SELECT pg_stat_get_backend_io(:checkpointer_pid);
+ pg_stat_get_backend_io 
+------------------------
+(0 rows)
+
 -- test BRIN index doesn't block HOT update
 CREATE TABLE brin_hot (
   id  integer PRIMARY KEY,
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 9c925005be..1e7d0ff665 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -595,7 +595,8 @@ SELECT pg_stat_get_replication_slot(NULL);
 SELECT pg_stat_get_subscription_stats(NULL);
 
 
--- Test that the following operations are tracked in pg_stat_io and in backend stats:
+-- Test that the following operations are tracked in pg_stat_io and in
+-- backend stats:
 -- - reads of target blocks into shared buffers
 -- - writes of shared buffers to permanent storage
 -- - extends of relations using shared buffers
@@ -650,10 +651,6 @@ SELECT current_setting('fsync') = 'off'
   OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
       AND :my_io_sum_shared_after_fsyncs= 0);
 
--- Don't return any rows if querying other backend's stats that are excluded
--- from the backend stats collection (like the checkpointer).
-SELECT count(1) = 0 FROM pg_stat_get_backend_io(:checkpointer_pid);
-
 -- Change the tablespace so that the table is rewritten directly, then SELECT
 -- from it to cause it to be read back into shared buffers.
 SELECT sum(reads) AS io_sum_shared_before_reads
@@ -801,6 +798,11 @@ SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) +
   FROM pg_stat_get_backend_io(pg_backend_pid()) \gset
 SELECT :my_io_stats_pre_reset > :my_io_stats_post_backend_reset;
 
+-- Check invalid input for pg_stat_get_backend_io()
+SELECT pg_stat_get_backend_io(NULL);
+SELECT pg_stat_get_backend_io(0);
+-- Auxiliary processes return no data.
+SELECT pg_stat_get_backend_io(:checkpointer_pid);
 
 -- test BRIN index doesn't block HOT update
 CREATE TABLE brin_hot (
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 4a9464da61..d0d176cc54 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -4801,11 +4801,13 @@ description | Waiting for a newly initialized WAL file to reach durable storage
        <para>
         Returns I/O statistics about the backend with the specified
         process ID. The output fields are exactly the same as the ones in the
-        <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
-        view. The function does not return I/O statistics for the checkpointer,
+        <structname>pg_stat_io</structname> view.
+       </para>
+       <para>
+        The function does not return I/O statistics for the checkpointer,
         the background writer, the startup process and the autovacuum launcher
-        as they are already visible in the <link linkend="monitoring-pg-stat-io-view"> <structname>pg_stat_io</structname></link>
-        view and there is only one of those.
+        as they are already visible in the <structname>pg_stat_io</structname>
+        view and there is only one of each.
        </para></entry>
       </row>
 
-- 
2.45.2



  [application/pgp-signature] signature.asc (833B, ../../[email protected]/4-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-18 08:11                     ` Bertrand Drouvot <[email protected]>
  2024-12-18 08:16                       ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 2 replies; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-18 08:11 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Wed, Dec 18, 2024 at 01:57:53PM +0900, Michael Paquier wrote:
> On Tue, Dec 17, 2024 at 09:35:37AM +0000, Bertrand Drouvot wrote:
> > Agree, we may need to add parameters to it but we'll see when the time comes.
> 
> Another thing that's been itching me: the loop in
> pg_stat_get_backend_io() that exists as well in pg_stat_get_io() looks
> worth refactoring in a single routine.  The only difference between
> both is the reset timestamp, still both of them can pass a value for
> it depending on their stats kind entry.

Yeah, I also had something like this in mind (see R2 in [1]). So, +1 for it.

> This shaves a bit more code
> in your own patch, even if the check on pgstat_tracks_io_bktype() and
> the Assert are not in the inner routine that fills the tuplestore with
> the I/O data.

Yeah, that's fine by me.

> See pg_stat_fill_io_data() in v10-0002.  If you have a
> better name for this routine, feel free..

I think I prefer pg_stat_io_build_tuples() and used that name in v11
attached.

> What do you think about this refactoring?

Makes fully sense to me (as per my first comment above).

> This should come in first,
> of course, so as the final patch introducing the backend stats is
> easier to parse.

Yeah, it's in 0001 attached, with a few changes:

=== 1

s/Save tuples with data from this PgStat_BktypeIO./save tuples with data from this PgStat_BktypeIO/

to be consistent with single line comments around.

=== 2

+  if (stat_reset_timestamp != 0)
+    values[IO_COL_RESET_TIME] = TimestampTzGetDatum(stat_reset_timestamp);
+  else
+    nulls[IO_COL_RESET_TIME] = true;

This is not necessary until the per backend I/O stat is introduced but I 
added it in 0001 to ease 0002 parsing.

=== 3

"In Assert builds, we can afford an extra loop through all of the"

I think that this comment is now confusing since the extra loop would be
done in pg_stat_io_build_tuples() and not where the comment is written. One
option could have been to move the assert and the comment in pg_stat_io_build_tuples()
but I think it's better to have the assert before the pgstat_tracks_io_bktype()
call in pg_stat_get_io(), so modified the comment a bit instead.

0002 is the per-backend stats I/O with yours tweaks.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com


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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-18 08:16                       ` Bertrand Drouvot <[email protected]>
  1 sibling, 0 replies; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-18 08:16 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Wed, Dec 18, 2024 at 08:11:55AM +0000, Bertrand Drouvot wrote:
> Yeah, I also had something like this in mind (see R2 in [1]). So, +1 for it.

[1] being: https://www.postgresql.org/message-id/Zy4bmvgHqGjcK1pI%40ip-10-97-1-34.eu-west-3.compute.internal

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com





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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-19 04:21                       ` Michael Paquier <[email protected]>
  2024-12-19 06:12                         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-20 06:00                         ` Re: per backend I/O statistics Alexander Lakhin <[email protected]>
  1 sibling, 2 replies; 68+ messages in thread

From: Michael Paquier @ 2024-12-19 04:21 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Wed, Dec 18, 2024 at 08:11:55AM +0000, Bertrand Drouvot wrote:
> I think I prefer pg_stat_io_build_tuples() and used that name in v11
> attached.

I'll rely to your naming sense than mine, then :D

> I think that this comment is now confusing since the extra loop would be
> done in pg_stat_io_build_tuples() and not where the comment is written. One
> option could have been to move the assert and the comment in pg_stat_io_build_tuples()
> but I think it's better to have the assert before the pgstat_tracks_io_bktype()
> call in pg_stat_get_io(), so modified the comment a bit instead.
> 
> 0002 is the per-backend stats I/O with yours tweaks.

While doing more tests with backends exiting concurrently with their
stats scanned, I have detected one path in pg_stat_get_backend_io()
after calling pgstat_get_beentry_by_proc_number() where we should also
check that it does not return NULL, or we would crash on a pointer
dereference when reading the backend type.

Fixed that, bumped the two version counters, and done.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-19 06:12                         ` Bertrand Drouvot <[email protected]>
  2024-12-20 09:57                           ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  1 sibling, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-19 06:12 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Thu, Dec 19, 2024 at 01:21:54PM +0900, Michael Paquier wrote:
> 
> While doing more tests with backends exiting concurrently with their
> stats scanned, I have detected one path in pg_stat_get_backend_io()
> after calling pgstat_get_beentry_by_proc_number() where we should also
> check that it does not return NULL, or we would crash on a pointer
> dereference when reading the backend type.
> 
> Fixed that,

Oh right, indeed all the others pgstat_get_beentry_by_proc_number() callers are
checking for a NULL returned value.

> bumped the two version counters, and done.

Thanks!

I think I'll start a dedicated thread to discuss the stats_fetch_consistency/'snapshot'
point (will be easier to follow than resuming the discussion in this thread).

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com





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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-19 06:12                         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-20 09:57                           ` Bertrand Drouvot <[email protected]>
  2024-12-24 05:35                             ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-20 09:57 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Thu, Dec 19, 2024 at 06:12:04AM +0000, Bertrand Drouvot wrote:
> On Thu, Dec 19, 2024 at 01:21:54PM +0900, Michael Paquier wrote:
> > bumped the two version counters, and done.

> >    The existing structure could be expanded in the
> >    future to add more information about other statistics related to
> >    backends, depending on requirements or ideas.

BTW, now that the per backend I/O statistics is done, I'll start working on per
backend wal statistics.

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com





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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-19 06:12                         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-20 09:57                           ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-24 05:35                             ` Michael Paquier <[email protected]>
  0 siblings, 0 replies; 68+ messages in thread

From: Michael Paquier @ 2024-12-24 05:35 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Fri, Dec 20, 2024 at 09:57:19AM +0000, Bertrand Drouvot wrote:
> BTW, now that the per backend I/O statistics is done, I'll start working on per
> backend wal statistics.

I think that this is a good idea.  It does not actually overlap the
proposal in [1] as the stats persistency is not the same.  What this
thread has taught me is that you could just plug in the stats of the
new backend-level structure a PgStat_WalStats and retrieve them with a
new function that returns a single tuple with the WAL stats
attributes.  That should be rather straight-forward to achieve.

[1]: https://commitfest.postgresql.org/51/4950/
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
@ 2024-12-20 06:00                         ` Alexander Lakhin <[email protected]>
  2024-12-20 09:09                           ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  1 sibling, 1 reply; 68+ messages in thread

From: Alexander Lakhin @ 2024-12-20 06:00 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; Bertrand Drouvot <[email protected]>; +Cc: Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hello Michael,

19.12.2024 06:21, Michael Paquier wrote:
> Fixed that, bumped the two version counters, and done.

Could you, please, look at recent failures produced by grassquit (which
has fsync = on in it's config), on an added test case? For instance, [1]:
--- /home/bf/bf-build/grassquit/HEAD/pgsql/src/test/regress/expected/stats.out 2024-12-19 04:44:08.779311933 +0000
+++ /home/bf/bf-build/grassquit/HEAD/pgsql.build/testrun/recovery/027_stream_regress/data/results/stats.out 2024-12-19 
16:37:41.351784840 +0000
@@ -1333,7 +1333,7 @@
        AND :my_io_sum_shared_after_fsyncs= 0);
   ?column?
  ----------
- t
+ f
  (1 row)

The complete query is:
SELECT current_setting('fsync') = 'off'
   OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
       AND :my_io_sum_shared_after_fsyncs= 0);

And the corresponding query in 027_stream_regress_primary.log is:
2024-12-19 16:37:39.907 UTC [4027467][client backend][15/1980:0] LOG:  statement: SELECT current_setting('fsync') = 'off'
       OR (1 = 1
           AND 1= 0);

(I can reproduce this locally with an asan-enabled build too.)

[1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=grassquit&dt=2024-12-19%2016%3A28%3A58

Best regards,
Alexander

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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-20 06:00                         ` Re: per backend I/O statistics Alexander Lakhin <[email protected]>
@ 2024-12-20 09:09                           ` Bertrand Drouvot <[email protected]>
  2024-12-20 09:24                             ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  0 siblings, 1 reply; 68+ messages in thread

From: Bertrand Drouvot @ 2024-12-20 09:09 UTC (permalink / raw)
  To: Alexander Lakhin <[email protected]>; +Cc: Michael Paquier <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

Hi,

On Fri, Dec 20, 2024 at 08:00:00AM +0200, Alexander Lakhin wrote:
> Hello Michael,
> 
> 19.12.2024 06:21, Michael Paquier wrote:
> > Fixed that, bumped the two version counters, and done.
> 
> Could you, please, look at recent failures produced by grassquit (which
> has fsync = on in it's config), on an added test case? For instance, [1]:
> --- /home/bf/bf-build/grassquit/HEAD/pgsql/src/test/regress/expected/stats.out 2024-12-19 04:44:08.779311933 +0000
> +++ /home/bf/bf-build/grassquit/HEAD/pgsql.build/testrun/recovery/027_stream_regress/data/results/stats.out
> 2024-12-19 16:37:41.351784840 +0000
> @@ -1333,7 +1333,7 @@
>        AND :my_io_sum_shared_after_fsyncs= 0);
>   ?column?
>  ----------
> - t
> + f
>  (1 row)
> 
> The complete query is:
> SELECT current_setting('fsync') = 'off'
>   OR (:my_io_sum_shared_after_fsyncs = :my_io_sum_shared_before_fsyncs
>       AND :my_io_sum_shared_after_fsyncs= 0);
> 
> And the corresponding query in 027_stream_regress_primary.log is:
> 2024-12-19 16:37:39.907 UTC [4027467][client backend][15/1980:0] LOG:  statement: SELECT current_setting('fsync') = 'off'
>       OR (1 = 1
>           AND 1= 0);
> 
> (I can reproduce this locally with an asan-enabled build too.)
> 
> [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=grassquit&dt=2024-12-19%2016%3A28%3A58

Thanks for the report! I was not able able to reproduce (even with asan-enabled)
but I think the test is wrong. Indeed the backend could fsync too during the test
(see register_dirty_segment() and the case where the request queue is full).

I think the test should look like the attached instead, thoughts?

Regards,

-- 
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com


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

* Re: per backend I/O statistics
  2024-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
  2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
  2024-12-20 06:00                         ` Re: per backend I/O statistics Alexander Lakhin <[email protected]>
  2024-12-20 09:09                           ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
@ 2024-12-20 09:24                             ` Michael Paquier <[email protected]>
  0 siblings, 0 replies; 68+ messages in thread

From: Michael Paquier @ 2024-12-20 09:24 UTC (permalink / raw)
  To: Bertrand Drouvot <[email protected]>; +Cc: Alexander Lakhin <[email protected]>; Nazir Bilal Yavuz <[email protected]>; Alvaro Herrera <[email protected]>; Kyotaro Horiguchi <[email protected]>; [email protected]

On Fri, Dec 20, 2024 at 09:09:00AM +0000, Bertrand Drouvot wrote:
> Thanks for the report! I was not able able to reproduce (even with asan-enabled)
> but I think the test is wrong. Indeed the backend could fsync too during the test
> (see register_dirty_segment() and the case where the request queue is full).
> 
> I think the test should look like the attached instead, thoughts?

Hmm.  I cannot reproduce that here either.  FWIW, I use these settings
by default in my local builds, similarly to the CI which did not
complain:
ASAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0"
UBSAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2"
CFLAGS+="-fsanitize=undefined "
LDFLAGS+="-fsanitize=undefined "

grassquit uses something a bit different, which don't allow me to see
a problem with 027 even with fsync enabled:
ASAN_OPTIONS="print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0"
CFLAGS+="-fsanitize=address -fno-sanitize-recover=all "
LDFLAGS+="-fsanitize=address -fno-sanitize-recover=all "

Anyway, I was arriving at the same conclusion as you, because this is
just telling us that there could be "some" sync activity.  As
rewritten, this would just check that the after-the-fact counter is
never lower than the before-the-fact counter, which is still better
than removing the query.  I was thinking about doing the latter and
remove the query, but I'm also OK with what you have here, keeping the
query with a relaxed check.  I'll go adjust that in a bit..
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

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


end of thread, other threads:[~2024-12-24 05:35 UTC | newest]

Thread overview: 68+ 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-12-12 04:52 Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-12 14:02 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-13 02:02   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-13 09:20     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-16 08:07       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-16 15:42         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-17 06:26           ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-17 08:13             ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-17 09:13               ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-17 09:35                 ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-18 04:57                   ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-18 08:11                     ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-18 08:16                       ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-19 04:21                       ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-19 06:12                         ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-20 09:57                           ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-24 05:35                             ` Re: per backend I/O statistics Michael Paquier <[email protected]>
2024-12-20 06:00                         ` Re: per backend I/O statistics Alexander Lakhin <[email protected]>
2024-12-20 09:09                           ` Re: per backend I/O statistics Bertrand Drouvot <[email protected]>
2024-12-20 09:24                             ` Re: per backend I/O statistics Michael Paquier <[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