agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH 1/3] anyarray_anyelement_operators 49+ messages / 2 participants [nested] [flat]
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH 1/3] anyarray_anyelement_operators @ 2021-03-13 10:01 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-13 10:01 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 43 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 375 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b7150510ab..9a3f79e3b7 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17525,6 +17525,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..b10bd04ec8 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,33 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = &PG_GETARG_DATUM(0); + nulls = &PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +137,14 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + /* + * only items that match the queried element + * are considered candidate + */ + + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -185,6 +204,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* we will need recheck */ *recheck = true; @@ -274,6 +294,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } } break; + case GinContainsElemStrategy: case GinContainedStrategy: /* can't do anything else useful here */ res = GIN_MAYBE; diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..f8cbf64c9e 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 0d4eac8f96..7ef071135c 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 4e0c9be58c..8bc05707c7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8180,6 +8180,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..95c9ae5443 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..698d322e14 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 3 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 0 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..b5eec945f7 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.17.0 --opg8F0UgoHELSI+9 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0002-fix.patch" ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* [PATCH v10 1/2] anyarray_anyelement_operators @ 2021-03-15 15:10 Mark Rofail <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Mark Rofail @ 2021-03-15 15:10 UTC (permalink / raw) --- doc/src/sgml/func.sgml | 28 +++++ doc/src/sgml/gin.sgml | 8 +- doc/src/sgml/indices.sgml | 2 +- src/backend/access/gin/ginarrayproc.c | 40 +++++-- src/backend/utils/adt/arrayfuncs.c | 137 +++++++++++++++++++++++ src/include/catalog/pg_amop.dat | 3 + src/include/catalog/pg_operator.dat | 14 ++- src/include/catalog/pg_proc.dat | 6 + src/test/regress/expected/arrays.out | 92 +++++++++++++++ src/test/regress/expected/gin.out | 34 ++++++ src/test/regress/expected/opr_sanity.out | 6 +- src/test/regress/sql/arrays.sql | 10 ++ src/test/regress/sql/gin.sql | 8 ++ 13 files changed, 372 insertions(+), 16 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 9492a3c6b9..04216e96a3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17539,6 +17539,34 @@ SELECT NULLIF(value, '(none)') ... </para></entry> </row> + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyarray</type> <literal>@>></literal> <type>anyelement</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Does the array contain the specified element? + </para> + <para> + <literal>ARRAY[1,4,3] @>> 3</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + + <row> + <entry role="func_table_entry"><para role="func_signature"> + <type>anyelement</type> <literal><<@</literal> <type>anyarray</type> + <returnvalue>boolean</returnvalue> + </para> + <para> + Is the specified element contained in the array? + </para> + <para> + <literal>2 <<@ ARRAY[1,7,4,2,6]</literal> + <returnvalue>t</returnvalue> + </para></entry> + </row> + <row> <entry role="func_table_entry"><para role="func_signature"> <type>anyarray</type> <literal>&&</literal> <type>anyarray</type> diff --git a/doc/src/sgml/gin.sgml b/doc/src/sgml/gin.sgml index d68d12d515..981513b765 100644 --- a/doc/src/sgml/gin.sgml +++ b/doc/src/sgml/gin.sgml @@ -84,7 +84,7 @@ </thead> <tbody> <row> - <entry morerows="3" valign="middle"><literal>array_ops</literal></entry> + <entry morerows="5" valign="middle"><literal>array_ops</literal></entry> <entry><literal>&& (anyarray,anyarray)</literal></entry> </row> <row> @@ -93,6 +93,12 @@ <row> <entry><literal><@ (anyarray,anyarray)</literal></entry> </row> + <row> + <entry><literal>@>> (anyarray,anyelement)</literal></entry> + </row> + <row> + <entry><literal><<@ (anyelement,anyarray)</literal></entry> + </row> <row> <entry><literal>= (anyarray,anyarray)</literal></entry> </row> diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 623962d1d8..6de6c33c75 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -326,7 +326,7 @@ SELECT * FROM places ORDER BY location <-> point '(101,456)' LIMIT 10; for arrays, which supports indexed queries using these operators: <synopsis> -<@ @> = && +<@ @> <<@ @>> = && </synopsis> (See <xref linkend="functions-array"/> for the meaning of diff --git a/src/backend/access/gin/ginarrayproc.c b/src/backend/access/gin/ginarrayproc.c index bf73e32932..eb7d13dbba 100644 --- a/src/backend/access/gin/ginarrayproc.c +++ b/src/backend/access/gin/ginarrayproc.c @@ -24,6 +24,7 @@ #define GinContainsStrategy 2 #define GinContainedStrategy 3 #define GinEqualStrategy 4 +#define GinContainsElemStrategy 5 /* @@ -78,8 +79,6 @@ ginarrayextract_2args(PG_FUNCTION_ARGS) Datum ginqueryarrayextract(PG_FUNCTION_ARGS) { - /* Make copy of array input to ensure it doesn't disappear while in use */ - ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); StrategyNumber strategy = PG_GETARG_UINT16(2); @@ -87,21 +86,35 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) /* Pointer *extra_data = (Pointer *) PG_GETARG_POINTER(4); */ bool **nullFlags = (bool **) PG_GETARG_POINTER(5); int32 *searchMode = (int32 *) PG_GETARG_POINTER(6); - int16 elmlen; - bool elmbyval; - char elmalign; Datum *elems; bool *nulls; int nelems; - get_typlenbyvalalign(ARR_ELEMTYPE(array), - &elmlen, &elmbyval, &elmalign); + if (strategy == GinContainsElemStrategy) + { + /* single element is passed, set elems to its pointer */ + elems = palloc(sizeof(*elems)); + *elems = PG_GETARG_DATUM(0); + nulls = palloc(sizeof(*nulls)); + *nulls = PG_ARGISNULL(0); + nelems = 1; + } + else + { + /* Make copy of array input to ensure it doesn't disappear while in use */ + ArrayType *array = PG_GETARG_ARRAYTYPE_P_COPY(0); + int16 elmlen; + bool elmbyval; + char elmalign; - deconstruct_array(array, - ARR_ELEMTYPE(array), - elmlen, elmbyval, elmalign, - &elems, &nulls, &nelems); + get_typlenbyvalalign(ARR_ELEMTYPE(array), + &elmlen, &elmbyval, &elmalign); + deconstruct_array(array, + ARR_ELEMTYPE(array), + elmlen, elmbyval, elmalign, + &elems, &nulls, &nelems); + } *nkeys = nelems; *nullFlags = nulls; @@ -126,6 +139,9 @@ ginqueryarrayextract(PG_FUNCTION_ARGS) else *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY; break; + case GinContainsElemStrategy: + *searchMode = GIN_SEARCH_MODE_DEFAULT; + break; default: elog(ERROR, "ginqueryarrayextract: unknown strategy number: %d", strategy); @@ -172,6 +188,7 @@ ginarrayconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* result is not lossy */ *recheck = false; /* must have all elements in check[] true, and no nulls */ @@ -259,6 +276,7 @@ ginarraytriconsistent(PG_FUNCTION_ARGS) } break; case GinContainsStrategy: + case GinContainsElemStrategy: /* must have all elements in check[] true, and no nulls */ res = GIN_TRUE; for (i = 0; i < nkeys; i++) diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 17a16b4c5c..518d3aaaf9 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -4328,6 +4328,143 @@ arraycontained(PG_FUNCTION_ARGS) } +/* + * array_contains_elem : checks an array for a specific element + * adapted from array_contain_compare() for containment of a single element + */ +static bool +array_contains_elem(AnyArrayType *array, Datum elem, Oid elemtype, + Oid collation, void **fn_extra) +{ + LOCAL_FCINFO(locfcinfo, 2); + Oid arrtype = AARR_ELEMTYPE(array); + TypeCacheEntry *typentry; + int nelems; + int typlen; + bool typbyval; + char typalign; + int i; + array_iter it; + + if (arrtype != elemtype) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot compare arrays elements with element of different type"))); + + /* + * We arrange to look up the equality function only once per series of + * calls, assuming the element type doesn't change underneath us. The + * typcache is used so that we have no memory leakage when being used as + * an index support function. + */ + typentry = (TypeCacheEntry *) *fn_extra; + if (typentry == NULL || + typentry->type_id != arrtype) + { + typentry = lookup_type_cache(arrtype, + TYPECACHE_EQ_OPR_FINFO); + if (!OidIsValid(typentry->eq_opr_finfo.fn_oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("could not identify an equality operator for type %s", + format_type_be(arrtype)))); + *fn_extra = (void *) typentry; + } + typlen = typentry->typlen; + typbyval = typentry->typbyval; + typalign = typentry->typalign; + + /* + * Apply the comparison operator for the passed element against each + * element in the array + */ + InitFunctionCallInfoData(*locfcinfo, &typentry->eq_opr_finfo, 2, + collation, NULL, NULL); + + /* Loop over source data */ + nelems = ArrayGetNItems(AARR_NDIM(array), AARR_DIMS(array)); + array_iter_setup(&it, array); + + for (i = 0; i < nelems; i++) + { + Datum elt; + bool isnull; + bool oprresult; + + /* Get element, checking for NULL */ + elt = array_iter_next(&it, &isnull, i, typlen, typbyval, typalign); + + /* + * We assume that the comparison operator is strict, so a NULL can't + * match anything. refer to the comment in array_contain_compare() + */ + if (isnull) + continue; + + /* + * Apply the operator to the element pair; treat NULL as false + */ + locfcinfo->args[0].value = elt; + locfcinfo->args[0].isnull = false; + locfcinfo->args[1].value = elem; + locfcinfo->args[1].isnull = false; + locfcinfo->isnull = false; + oprresult = DatumGetBool(FunctionCallInvoke(locfcinfo)); + if (!locfcinfo->isnull && oprresult) + return true; + } + + return false; +} + +Datum +arraycontainselem(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(0); + Datum elem = PG_GETARG_DATUM(1); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 0); + + PG_RETURN_BOOL(result); +} + +Datum +arrayelemcontained(PG_FUNCTION_ARGS) +{ + AnyArrayType *array = PG_GETARG_ANY_ARRAY_P(1); + Datum elem = PG_GETARG_DATUM(0); + Oid elemtype = get_fn_expr_argtype(fcinfo->flinfo, 0); + Oid collation = PG_GET_COLLATION(); + bool result; + + /* + * we don't need to check if the elem is null or if the elem datatype and + * array datatype match since this is handled within internal calls already + * (a property of polymorphic functions) + */ + + result = array_contains_elem(array, elem, elemtype, collation, + &fcinfo->flinfo->fn_extra); + + /* Avoid leaking memory when handed toasted input */ + AARR_FREE_IF_COPY(array, 1); + + PG_RETURN_BOOL(result); +} + /*----------------------------------------------------------------------------- * Array iteration functions * These functions are used to iterate efficiently through arrays diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat index 0f7ff63669..8a14fc7140 100644 --- a/src/include/catalog/pg_amop.dat +++ b/src/include/catalog/pg_amop.dat @@ -1242,6 +1242,9 @@ { amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', amoprighttype => 'anyarray', amopstrategy => '4', amopopr => '=(anyarray,anyarray)', amopmethod => 'gin' }, +{ amopfamily => 'gin/array_ops', amoplefttype => 'anyarray', + amoprighttype => 'anyelement', amopstrategy => '5', + amopopr => '@>>(anyarray,anyelement)', amopmethod => 'gin' }, # btree enum_ops { amopfamily => 'btree/enum_ops', amoplefttype => 'anyenum', diff --git a/src/include/catalog/pg_operator.dat b/src/include/catalog/pg_operator.dat index 85395a81ee..c6e809b88d 100644 --- a/src/include/catalog/pg_operator.dat +++ b/src/include/catalog/pg_operator.dat @@ -2761,7 +2761,7 @@ oprresult => 'bool', oprcode => 'circle_overabove', oprrest => 'positionsel', oprjoin => 'positionjoinsel' }, -# overlap/contains/contained for arrays +# overlap/contains/contained/elemcontained/containselem for arrays { oid => '2750', oid_symbol => 'OID_ARRAY_OVERLAP_OP', descr => 'overlaps', oprname => '&&', oprleft => 'anyarray', oprright => 'anyarray', oprresult => 'bool', oprcom => '&&(anyarray,anyarray)', @@ -2778,6 +2778,18 @@ oprresult => 'bool', oprcom => '@>(anyarray,anyarray)', oprcode => 'arraycontained', oprrest => 'arraycontsel', oprjoin => 'arraycontjoinsel' }, +{ oid => '6108', oid_symbol => 'OID_ARRAY_ELEMCONTAINED_OP', + descr => 'elem is contained by', + oprname => '<<@', oprleft => 'anyelement', oprright => 'anyarray', + oprresult => 'bool', oprcom => '@>>(anyarray,anyelement)', + oprcode => 'arrayelemcontained', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, +{ oid => '6105', oid_symbol => 'OID_ARRAY_CONTAINSELEM_OP', + descr => 'contains elem', + oprname => '@>>', oprleft => 'anyarray', oprright => 'anyelement', + oprresult => 'bool', oprcom => '<<@(anyelement,anyarray)', + oprcode => 'arraycontainselem', oprrest => 'arraycontsel', + oprjoin => 'arraycontjoinsel' }, # capturing operators to preserve pre-8.3 behavior of text concatenation { oid => '2779', descr => 'concatenate', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 93393fcfd4..8d82e64f86 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8200,6 +8200,12 @@ { oid => '2749', proname => 'arraycontained', prorettype => 'bool', proargtypes => 'anyarray anyarray', prosrc => 'arraycontained' }, +{ oid => '6109', + proname => 'arrayelemcontained', prorettype => 'bool', + proargtypes => 'anyelement anyarray', prosrc => 'arrayelemcontained' }, +{ oid => '6107', + proname => 'arraycontainselem', prorettype => 'bool', + proargtypes => 'anyarray anyelement', prosrc => 'arraycontainselem' }, # BRIN minmax { oid => '3383', descr => 'BRIN minmax support', diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 3e3a1beaab..03ce07e219 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -758,6 +758,28 @@ SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} (6 rows) +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 74 | {32} | {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} + 98 | {38,34,32,89} | {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845} + 100 | {85,32,57,39,49,84,32,3,30} | {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523} +(6 rows) + SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -782,6 +804,32 @@ SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} (8 rows) +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; + seqno | i | t +-------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ + 6 | {39,35,5,94,17,92,60,32} | {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657} + 12 | {17,99,18,52,91,72,0,43,96,23} | {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576} + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 19 | {52,82,17,74,23,46,69,51,75} | {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938} + 53 | {38,17} | {AAAAAAAAAAA21658} + 65 | {61,5,76,59,17} | {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012} + 77 | {97,15,32,17,55,59,18,37,50,39} | {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066} + 89 | {40,32,17,6,30,88} | {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673} +(8 rows) + SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; seqno | i | t -------+---------------------------------+------------------------------------------------------------------------------------------------------------------------------------ @@ -963,6 +1011,16 @@ SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; -------+---+--- (0 rows) +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; + seqno | i | t +-------+---+--- +(0 rows) + SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; seqno | i | t -------+---+--- @@ -983,6 +1041,24 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} (4 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; + seqno | i | t +-------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- + 22 | {11,6,56,62,53,30} | {AAAAAAAA72908} + 45 | {99,45} | {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611} + 72 | {22,1,16,78,20,91,83} | {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} +(4 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; seqno | i | t -------+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------- @@ -1000,6 +1076,22 @@ SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; 96 | {23,97,43} | {AAAAAAAAAA646,A87088} (3 rows) +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; + seqno | i | t +-------+------------------+-------------------------------------------------------------------- + 15 | {17,14,16,63,67} | {AA6416,AAAAAAAAAA646,AAAAA95309} + 79 | {45} | {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908} + 96 | {23,97,43} | {AAAAAAAAAA646,A87088} +(3 rows) + SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; seqno | i | t -------+------------------+-------------------------------------------------------------------- diff --git a/src/test/regress/expected/gin.out b/src/test/regress/expected/gin.out index 6402e89c7f..7fc7436646 100644 --- a/src/test/regress/expected/gin.out +++ b/src/test/regress/expected/gin.out @@ -53,6 +53,40 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; 3 (1 row) +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 1) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 1) +(5 rows) + +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + QUERY PLAN +----------------------------------------------- + Aggregate + -> Bitmap Heap Scan on gin_test_tbl + Recheck Cond: (i @>> 999) + -> Bitmap Index Scan on gin_test_idx + Index Cond: (i @>> 999) +(5 rows) + +select count(*) from gin_test_tbl where i @>> 1; + count +------- + 2997 +(1 row) + +select count(*) from gin_test_tbl where i @>> 999; + count +------- + 3 +(1 row) + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; explain (costs off) diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 254ca06d3d..5de5ab6d13 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1173,6 +1173,7 @@ ORDER BY 1, 2; <-> | <-> << | >> <<= | >>= + <<@ | @>> <= | >= <> | <> <@ | @> @@ -1188,7 +1189,7 @@ ORDER BY 1, 2; ~<=~ | ~>=~ ~<~ | ~>~ ~= | ~= -(29 rows) +(30 rows) -- Likewise for negator pairs. SELECT DISTINCT o1.oprname AS op1, o2.oprname AS op2 @@ -2029,6 +2030,7 @@ ORDER BY 1, 2, 3; 2742 | 2 | @@@ 2742 | 3 | <@ 2742 | 4 | = + 2742 | 5 | @>> 2742 | 7 | @> 2742 | 9 | ? 2742 | 10 | ?| @@ -2100,7 +2102,7 @@ ORDER BY 1, 2, 3; 4000 | 28 | ^@ 4000 | 29 | <^ 4000 | 30 | >^ -(123 rows) +(124 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 912233ef96..944fa3afdd 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -319,8 +319,12 @@ SELECT 0 || ARRAY[1,2] || 3 AS "{0,1,2,3}"; SELECT ARRAY[1.1] || ARRAY[2,3,4]; SELECT * FROM array_op_test WHERE i @> '{32}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 32 ORDER BY seqno; +SELECT * FROM array_op_test WHERE 32 <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{17}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 17 <<@ i ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> 17 ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{32,17}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{32,17}' ORDER BY seqno; @@ -331,12 +335,18 @@ SELECT * FROM array_op_test WHERE i && '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i = '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i @> '{NULL}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE i @>> NULL ORDER BY seqno; +SELECT * FROM array_op_test WHERE NULL <<@ i ORDER BY seqno; SELECT * FROM array_op_test WHERE i && '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE i <@ '{NULL}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAA72908' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAA72908' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAAAA646}' ORDER BY seqno; +SELECT * FROM array_op_test WHERE t @>> 'AAAAAAAAAA646' ORDER BY seqno; +SELECT * FROM array_op_test WHERE 'AAAAAAAAAA646' <<@ t ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t @> '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; SELECT * FROM array_op_test WHERE t && '{AAAAAAAA72908,AAAAAAAAAA646}' ORDER BY seqno; diff --git a/src/test/regress/sql/gin.sql b/src/test/regress/sql/gin.sql index 5194afcc1f..c9b40903c6 100644 --- a/src/test/regress/sql/gin.sql +++ b/src/test/regress/sql/gin.sql @@ -41,6 +41,14 @@ select count(*) from gin_test_tbl where i @> array[1, 999]; select count(*) from gin_test_tbl where i @> array[1, 999]; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 1; +explain (costs off) +select count(*) from gin_test_tbl where i @>> 999; + +select count(*) from gin_test_tbl where i @>> 1; +select count(*) from gin_test_tbl where i @>> 999; + -- Very weak test for gin_fuzzy_search_limit set gin_fuzzy_search_limit = 1000; -- 2.30.1 --------------3A927D779D0A0B91F132AD46-- ^ permalink raw reply [nested|flat] 49+ messages in thread
* Re: regression coverage gaps for gist and hash indexes @ 2023-04-17 08:00 Alexander Lakhin <[email protected]> 0 siblings, 0 replies; 49+ messages in thread From: Alexander Lakhin @ 2023-04-17 08:00 UTC (permalink / raw) To: Tom Lane <[email protected]>; Andres Freund <[email protected]>; +Cc: [email protected]; Drouvot, Bertrand <[email protected]>; Andrey Borodin <[email protected]> Hi, 31.03.2023 17:00, Alexander Lakhin wrote: > 31.03.2023 15:55, Tom Lane wrote: >> See also the thread about bug #16329 [1]. Alexander promised to look >> into improving the test coverage in this area, maybe he can keep an >> eye on the WAL logic coverage too. > > Yes, I'm going to analyze that area too. Maybe it'll take more time > (a week or two) if I encounter some bugs there (for now I observe anomalies > with gist__int_ops), but I will definitely try to improve the gist testing. After 2+ weeks of researching I'd like to summarize my findings. 1) The checking query proposed in [1] could be improved by adding the restriction "tgk.v = brute.v" to the condition: WHERE tgk.k >> point(brute.min - 1, 0) AND tgk.k << point(brute.max + 1, 0) Otherwise that query gives a false positive after insert into test_gist_killtuples values(point(505, 0)); The similar improved condition could be placed in hash_index_killtuples.sql. Yet another improvement for the checking query could be done with the replacement: min(k <-> point(0, 0)), max(k <-> point(0, 0)) -> min(k <-> point(0, k[1])), max(p <-> point(0, k[1])) ... It doesn't change the query plan dramatically, but the query becomes more universal (it would work for points with any non-negative integer x). 2) I've checked clang`s scan-build notices related to gist as I planned [2], namely: Logic error Branch condition evaluates to a garbage value src/backend/access/gist/gistutil.c gistCompressValues 606 Logic error Dereference of null pointer src/backend/access/gist/gist.c gistFindCorrectParent 1099 Logic error Dereference of null pointer src/backend/access/gist/gist.c gistdoinsert 671 Logic error Dereference of null pointer src/backend/access/gist/gist.c gistfinishsplit 1339 Logic error Dereference of null pointer src/backend/access/gist/gist.c gistplacetopage 340 Logic error Dereference of null pointer src/backend/access/gist/gistbuildbuffers.c gistPushItupToNodeBuffer 366 Logic error Result of operation is garbage or undefined src/backend/access/gist/gistbuildbuffers.c gistRelocateBuildBuffersOnSplit 677 Logic error Result of operation is garbage or undefined src/backend/access/gist/gistutil.c gistchoose 463 Unused code Dead assignment src/backend/access/gist/gist.c gistdoinsert 843 And found that all of them (except for the last one, that doesn't worth fixing, IMO) are false positives (I can present detailed explanations if it could be of interest.) So I see no grounds here to build new tests on. 3) To date I found other anomalies more or less related to gist: fillfactor is ignored for sorted index build mode, which is effectively default now [3] amcheck verification for gist is not yet ready to use [4] (and the latest patch doesn't apply to the current HEAD) bug #17888: Incorrect memory access in gist__int_ops for an input array with many elements [5] 4) I've constructed some tests, that provide full coverage for gistFindCorrectParent(), reach for "very rare situation", and for gistfixsplit(), but all these tests execute concurrent queries, so they can't be implemented as simple regression tests. Moreover, I could not find any explicit problems when reaching those places (I used the checking query from [1] in absence of other means to check gist indexes), so I see no value in developing (not to speak of committing) these tests for now. I'm going to further explore the gist behavior in those dark corners, but it looks like a long-term task, so I think it shouldn't delay the gist coverage improvement already proposed. 5) 02.04.2023 20:50, Andres Freund wrote: > Looks like the test in [1] could be made a lot cheaper by changing effective_cache_size > for just that test: The effective_cache_size is accounted only when buffering = auto, but in that test we have buffering = on, so changing it wouldn't help there. While looking at gist-related tests, I've noticed an incorrect comment in index_including_gist.sql: * 1.1. test CREATE INDEX with buffered build It's incorrect exactly because with the default effective_cache_size the buffered build mode is not enabled for that index size (I've confirmed this with the elog(LOG,..) placed inside gistInitBuffering()). So I'd like to propose the patch attached, that: a) demonstrates the bug #16329: With 8e5eef50c reverted, I get: **00:00:00:11.179 1587838** Valgrind detected 1 error(s) during execution of "CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3) WITH (buffering = on);" b) makes the comment in index_including_gist.sql correct c) increases a visible test coverage a little, in particular: Function 'gistBuffersReleaseBlock' -Lines executed:66.67% of 9 +Lines executed:100.00% of 9 d) doesn't increase the test duration significantly: without valgrind I see difference: 84 ms -> 93 ms, under vagrind: 13513 ms -> 14511 ms Thus, I think, it's worth to split the activity related to gist testing improvement to finalizing/accepting the already-emerging patches and to background research/anomaly findings, which could inspire further enhancements in this area. [1] https://www.postgresql.org/message-id/20230331231300.4kkrl44usvy2pmkv%40awork3.anarazel.de [2] https://www.postgresql.org/message-id/cad7055f-0d76-cc31-71d5-f8b600ebb116%40gmail.com [3] https://www.postgresql.org/message-id/fbbfe5dc-3dfa-d54a-3a94-e2bee37b85d8%40gmail.com [4] https://www.postgresql.org/message-id/885cfb61-26e9-e7c1-49a8-02b3fb12b497%40gmail.com [5] https://www.postgresql.org/message-id/[email protected] Best regards, Alexander Attachments: [text/x-patch] 0002-WIP-Improve-gist-testing-per-bug-#16329.patch (3.8K, ../../[email protected]/2-0002-WIP-Improve-gist-testing-per-bug-%2316329.patch) download | inline diff: diff --git a/src/test/regress/expected/index_including_gist.out b/src/test/regress/expected/index_including_gist.out index ed9906da66..953b4ebd8b 100644 --- a/src/test/regress/expected/index_including_gist.out +++ b/src/test/regress/expected/index_including_gist.out @@ -2,25 +2,25 @@ * 1.1. test CREATE INDEX with buffered build */ -- Regular index with included columns -CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box); --- size is chosen to exceed page size and trigger actual truncation -INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x; -CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3); +CREATE TABLE tbl_gist (c1 int, c2 int, c3 text, c4 box); +-- size is chosen to trigger buffer emptying (bug #16329). +INSERT INTO tbl_gist SELECT x, 2*x, repeat('x', 100), box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x; +CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3) WITH (buffering = on); SELECT pg_get_indexdef(i.indexrelid) FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname; - pg_get_indexdef ------------------------------------------------------------------------------------ - CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3) + pg_get_indexdef +--------------------------------------------------------------------------------------------------------- + CREATE INDEX tbl_gist_idx ON public.tbl_gist USING gist (c4) INCLUDE (c1, c2, c3) WITH (buffering='on') (1 row) SELECT * FROM tbl_gist where c4 <@ box(point(1,1),point(10,10)); - c1 | c2 | c3 | c4 -----+----+----+------------- - 1 | 2 | 3 | (2,3),(1,2) - 2 | 4 | 6 | (4,5),(2,3) - 3 | 6 | 9 | (6,7),(3,4) - 4 | 8 | 12 | (8,9),(4,5) + c1 | c2 | c3 | c4 +----+----+------------------------------------------------------------------------------------------------------+------------- + 1 | 2 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | (2,3),(1,2) + 2 | 4 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | (4,5),(2,3) + 3 | 6 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | (6,7),(3,4) + 4 | 8 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | (8,9),(4,5) (4 rows) SET enable_bitmapscan TO off; diff --git a/src/test/regress/sql/index_including_gist.sql b/src/test/regress/sql/index_including_gist.sql index 7d5c99b2e7..60ba15f8f0 100644 --- a/src/test/regress/sql/index_including_gist.sql +++ b/src/test/regress/sql/index_including_gist.sql @@ -3,10 +3,10 @@ */ -- Regular index with included columns -CREATE TABLE tbl_gist (c1 int, c2 int, c3 int, c4 box); --- size is chosen to exceed page size and trigger actual truncation -INSERT INTO tbl_gist SELECT x, 2*x, 3*x, box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x; -CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3); +CREATE TABLE tbl_gist (c1 int, c2 int, c3 text, c4 box); +-- size is chosen to trigger buffer emptying (bug #16329). +INSERT INTO tbl_gist SELECT x, 2*x, repeat('x', 100), box(point(x,x+1),point(2*x,2*x+1)) FROM generate_series(1,8000) AS x; +CREATE INDEX tbl_gist_idx ON tbl_gist using gist (c4) INCLUDE (c1,c2,c3) WITH (buffering = on); SELECT pg_get_indexdef(i.indexrelid) FROM pg_index i JOIN pg_class c ON i.indexrelid = c.oid WHERE i.indrelid = 'tbl_gist'::regclass ORDER BY c.relname; ^ permalink raw reply [nested|flat] 49+ messages in thread
end of thread, other threads:[~2023-04-17 08:00 UTC | newest] Thread overview: 49+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-13 10:01 [PATCH 1/3] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2021-03-15 15:10 [PATCH v10 1/2] anyarray_anyelement_operators Mark Rofail <[email protected]> 2023-04-17 08:00 Re: regression coverage gaps for gist and hash indexes Alexander Lakhin <[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