From 0a687c8f103d217ff1ca8c34a644b380d89bb0ad Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 3 Mar 2021 16:39:57 -0500 Subject: [PATCH] Add trim_array() function. This has been in the SQL spec since 2008. It's a pretty thin wrapper around the array slice functionality, but the spec says we should have it, so here it is. Vik Fearing, reviewed by Dian Fay Discussion: https://postgr.es/m/fc92ce17-9655-8ff1-c62a-4dc4c8ccd815@postgresfriends.org --- doc/src/sgml/func.sgml | 18 ++++++++++++ src/backend/catalog/sql_features.txt | 2 +- src/backend/utils/adt/arrayfuncs.c | 43 ++++++++++++++++++++++++++++ src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_proc.dat | 3 ++ src/test/regress/expected/arrays.out | 21 ++++++++++++++ src/test/regress/sql/arrays.sql | 13 +++++++++ 7 files changed, 100 insertions(+), 2 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index bf99f82149..fee0561961 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -17930,6 +17930,24 @@ SELECT NULLIF(value, '(none)') ... + + + + trim_array + + trim_array ( array anyarray, n integer ) + anyarray + + + Trims an array by removing the last n elements. + If the array is multidimensional, only the first dimension is trimmed. + + + trim_array(ARRAY[1,2,3,4,5,6], 2) + {1,2,3,4} + + + diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index ab0895ce3c..32eed988ab 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -398,7 +398,7 @@ S301 Enhanced UNNEST YES S401 Distinct types based on array types NO S402 Distinct types based on distinct types NO S403 ARRAY_MAX_CARDINALITY NO -S404 TRIM_ARRAY NO +S404 TRIM_ARRAY YES T011 Timestamp in Information Schema NO T021 BINARY and VARBINARY data types NO T022 Advanced support for BINARY and VARBINARY data types NO diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index f7012cc5d9..17a16b4c5c 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -6631,3 +6631,46 @@ width_bucket_array_variable(Datum operand, return left; } + +/* + * Trim the last N elements from an array by building an appropriate slice. + * Only the first dimension is trimmed. + */ +Datum +trim_array(PG_FUNCTION_ARGS) +{ + ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); + int n = PG_GETARG_INT32(1); + int array_length = ARR_DIMS(v)[0]; + int16 elmlen; + bool elmbyval; + char elmalign; + int lower[MAXDIM]; + int upper[MAXDIM]; + bool lowerProvided[MAXDIM]; + bool upperProvided[MAXDIM]; + Datum result; + + /* Per spec, throw an error if out of bounds */ + if (n < 0 || n > array_length) + ereport(ERROR, + (errcode(ERRCODE_ARRAY_ELEMENT_ERROR), + errmsg("number of elements to trim must be between 0 and %d", + array_length))); + + /* Set all the bounds as unprovided except the first upper bound */ + memset(lowerProvided, false, sizeof(lowerProvided)); + memset(upperProvided, false, sizeof(upperProvided)); + upper[0] = ARR_LBOUND(v)[0] + array_length - n - 1; + upperProvided[0] = true; + + /* Fetch the needed information about the element type */ + get_typlenbyvalalign(ARR_ELEMTYPE(v), &elmlen, &elmbyval, &elmalign); + + /* Get the slice */ + result = array_get_slice(PointerGetDatum(v), 1, + upper, lower, upperProvided, lowerProvided, + -1, elmlen, elmbyval, elmalign); + + PG_RETURN_DATUM(result); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b19975c5c8..28b8a32129 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202103031 +#define CATALOG_VERSION_NO 202103032 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 3d3974f467..59d2b71ca9 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -1663,6 +1663,9 @@ proname => 'width_bucket', prorettype => 'int4', proargtypes => 'anycompatible anycompatiblearray', prosrc => 'width_bucket_array' }, +{ oid => '8819', descr => 'remove last N elements of array', + proname => 'trim_array', prorettype => 'anyarray', + proargtypes => 'anyarray int4', prosrc => 'trim_array' }, { oid => '3816', descr => 'array typanalyze', proname => 'array_typanalyze', provolatile => 's', prorettype => 'bool', proargtypes => 'internal', prosrc => 'array_typanalyze' }, diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 8bc7721e7d..3e3a1beaab 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -2399,3 +2399,24 @@ SELECT width_bucket(5, ARRAY[3, 4, NULL]); ERROR: thresholds array must not contain NULLs SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]); ERROR: thresholds must be one-dimensional array +-- trim_array +SELECT arr, trim_array(arr, 2) +FROM +(VALUES ('{1,2,3,4,5,6}'::bigint[]), + ('{1,2}'), + ('[10:16]={1,2,3,4,5,6,7}'), + ('[-15:-10]={1,2,3,4,5,6}'), + ('{{1,10},{2,20},{3,30},{4,40}}')) v(arr); + arr | trim_array +-------------------------------+----------------- + {1,2,3,4,5,6} | {1,2,3,4} + {1,2} | {} + [10:16]={1,2,3,4,5,6,7} | {1,2,3,4,5} + [-15:-10]={1,2,3,4,5,6} | {1,2,3,4} + {{1,10},{2,20},{3,30},{4,40}} | {{1,10},{2,20}} +(5 rows) + +SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail +ERROR: number of elements to trim must be between 0 and 3 +SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail +ERROR: number of elements to trim must be between 0 and 3 diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index c40619a8d5..912233ef96 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -722,3 +722,16 @@ SELECT width_bucket(5, '{}'); SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]); SELECT width_bucket(5, ARRAY[3, 4, NULL]); SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]); + +-- trim_array + +SELECT arr, trim_array(arr, 2) +FROM +(VALUES ('{1,2,3,4,5,6}'::bigint[]), + ('{1,2}'), + ('[10:16]={1,2,3,4,5,6,7}'), + ('[-15:-10]={1,2,3,4,5,6}'), + ('{{1,10},{2,20},{3,30},{4,40}}')) v(arr); + +SELECT trim_array(ARRAY[1, 2, 3], -1); -- fail +SELECT trim_array(ARRAY[1, 2, 3], 10); -- fail