]> granicus.if.org Git - postgresql/commitdiff
Add array_remove() and array_replace() functions.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 11 Jul 2012 17:59:35 +0000 (13:59 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 11 Jul 2012 17:59:35 +0000 (13:59 -0400)
These functions support removing or replacing array element value(s)
matching a given search value.  Although intended mainly to support a
future array-foreign-key feature, they seem useful in their own right.

Marco Nenciarini and Gabriele Bartolini, reviewed by Alex Hunsaker

doc/src/sgml/func.sgml
src/backend/utils/adt/arrayfuncs.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/utils/array.h
src/test/regress/expected/arrays.out
src/test/regress/sql/arrays.sql

index 4f539428e28a4aa81f96fb5ad65c9b514c391b2c..157de09b4ea213156679dd62a121bc0274f4c127 100644 (file)
@@ -10316,6 +10316,12 @@ SELECT NULLIF(value, '(none)') ...
   <indexterm>
     <primary>array_prepend</primary>
   </indexterm>
+  <indexterm>
+    <primary>array_remove</primary>
+  </indexterm>
+  <indexterm>
+    <primary>array_replace</primary>
+  </indexterm>
   <indexterm>
     <primary>array_to_string</primary>
   </indexterm>
@@ -10432,6 +10438,29 @@ SELECT NULLIF(value, '(none)') ...
         <entry><literal>array_prepend(1, ARRAY[2,3])</literal></entry>
         <entry><literal>{1,2,3}</literal></entry>
        </row>
+       <row>
+        <entry>
+         <literal>
+          <function>array_remove</function>(<type>anyarray</type>, <type>anyelement</type>)
+         </literal>
+        </entry>
+        <entry><type>anyarray</type></entry>
+        <entry>remove all elements equal to the given value from the array
+         (array must be one-dimensional)</entry>
+        <entry><literal>array_remove(ARRAY[1,2,3,2], 2)</literal></entry>
+        <entry><literal>{1,3}</literal></entry>
+       </row>
+       <row>
+        <entry>
+         <literal>
+          <function>array_replace</function>(<type>anyarray</type>, <type>anyelement</type>, <type>anyelement</type>)
+         </literal>
+        </entry>
+        <entry><type>anyarray</type></entry>
+        <entry>replace each array element equal to the given value with a new value</entry>
+        <entry><literal>array_replace(ARRAY[1,2,5,4], 5, 3)</literal></entry>
+        <entry><literal>{1,2,3,4}</literal></entry>
+       </row>
        <row>
         <entry>
          <literal>
index c221c20c9f6519abb500426e444524190f22b18a..e14906fd98da0e96190de9bff798f9b53cc04fde 100644 (file)
@@ -124,6 +124,11 @@ static ArrayType *create_array_envelope(int ndims, int *dimv, int *lbv, int nbyt
 static ArrayType *array_fill_internal(ArrayType *dims, ArrayType *lbs,
                                        Datum value, bool isnull, Oid elmtype,
                                        FunctionCallInfo fcinfo);
+static ArrayType *array_replace_internal(ArrayType *array,
+                                          Datum search, bool search_isnull,
+                                          Datum replace, bool replace_isnull,
+                                          bool remove, Oid collation,
+                                          FunctionCallInfo fcinfo);
 
 
 /*
@@ -5174,3 +5179,304 @@ array_unnest(PG_FUNCTION_ARGS)
                SRF_RETURN_DONE(funcctx);
        }
 }
+
+
+/*
+ * array_replace/array_remove support
+ *
+ * Find all array entries matching (not distinct from) search/search_isnull,
+ * and delete them if remove is true, else replace them with
+ * replace/replace_isnull.  Comparisons are done using the specified
+ * collation.  fcinfo is passed only for caching purposes.
+ */
+static ArrayType *
+array_replace_internal(ArrayType *array,
+                                          Datum search, bool search_isnull,
+                                          Datum replace, bool replace_isnull,
+                                          bool remove, Oid collation,
+                                          FunctionCallInfo fcinfo)
+{
+       ArrayType  *result;
+       Oid                     element_type;
+       Datum      *values;
+       bool       *nulls;
+       int                *dim;
+       int                     ndim;
+       int                     nitems,
+                               nresult;
+       int                     i;
+       int32           nbytes = 0;
+       int32           dataoffset;
+       bool            hasnulls;
+       int                     typlen;
+       bool            typbyval;
+       char            typalign;
+       char       *arraydataptr;
+       bits8      *bitmap;
+       int                     bitmask;
+       bool            changed = false;
+       TypeCacheEntry *typentry;
+       FunctionCallInfoData locfcinfo;
+
+       element_type = ARR_ELEMTYPE(array);
+       ndim = ARR_NDIM(array);
+       dim = ARR_DIMS(array);
+       nitems = ArrayGetNItems(ndim, dim);
+
+       /* Return input array unmodified if it is empty */
+       if (nitems <= 0)
+               return array;
+
+       /*
+        * We can't remove elements from multi-dimensional arrays, since the
+        * result might not be rectangular.
+        */
+       if (remove && ndim > 1)
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("removing elements from multidimensional arrays is not supported")));
+
+       /*
+        * We arrange to look up the equality function only once per series of
+        * calls, assuming the element type doesn't change underneath us.
+        */
+       typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
+       if (typentry == NULL ||
+               typentry->type_id != element_type)
+       {
+               typentry = lookup_type_cache(element_type,
+                                                                        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(element_type))));
+               fcinfo->flinfo->fn_extra = (void *) typentry;
+       }
+       typlen = typentry->typlen;
+       typbyval = typentry->typbyval;
+       typalign = typentry->typalign;
+
+       /*
+        * Detoast values if they are toasted.  The replacement value must be
+        * detoasted for insertion into the result array, while detoasting the
+        * search value only once saves cycles.
+        */
+       if (typlen == -1)
+       {
+               if (!search_isnull)
+                       search = PointerGetDatum(PG_DETOAST_DATUM(search));
+               if (!replace_isnull)
+                       replace = PointerGetDatum(PG_DETOAST_DATUM(replace));
+       }
+
+       /* Prepare to apply the comparison operator */
+       InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
+                                                        collation, NULL, NULL);
+
+       /* Allocate temporary arrays for new values */
+       values = (Datum *) palloc(nitems * sizeof(Datum));
+       nulls = (bool *) palloc(nitems * sizeof(bool));
+
+       /* Loop over source data */
+       arraydataptr = ARR_DATA_PTR(array);
+       bitmap = ARR_NULLBITMAP(array);
+       bitmask = 1;
+       hasnulls = false;
+       nresult = 0;
+
+       for (i = 0; i < nitems; i++)
+       {
+               Datum           elt;
+               bool            isNull;
+               bool            oprresult;
+               bool            skip = false;
+
+               /* Get source element, checking for NULL */
+               if (bitmap && (*bitmap & bitmask) == 0)
+               {
+                       isNull = true;
+                       /* If searching for NULL, we have a match */
+                       if (search_isnull)
+                       {
+                               if (remove)
+                               {
+                                       skip = true;
+                                       changed = true;
+                               }
+                               else if (!replace_isnull)
+                               {
+                                       values[nresult] = replace;
+                                       isNull = false;
+                                       changed = true;
+                               }
+                       }
+               }
+               else
+               {
+                       isNull = false;
+                       elt = fetch_att(arraydataptr, typbyval, typlen);
+                       arraydataptr = att_addlength_datum(arraydataptr, typlen, elt);
+                       arraydataptr = (char *) att_align_nominal(arraydataptr, typalign);
+
+                       if (search_isnull)
+                       {
+                               /* no match possible, keep element */
+                               values[nresult] = elt;
+                       }
+                       else
+                       {
+                               /*
+                                * Apply the operator to the element pair
+                                */
+                               locfcinfo.arg[0] = elt;
+                               locfcinfo.arg[1] = search;
+                               locfcinfo.argnull[0] = false;
+                               locfcinfo.argnull[1] = false;
+                               locfcinfo.isnull = false;
+                               oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
+                               if (!oprresult)
+                               {
+                                       /* no match, keep element */
+                                       values[nresult] = elt;
+                               }
+                               else
+                               {
+                                       /* match, so replace or delete */
+                                       changed = true;
+                                       if (remove)
+                                               skip = true;
+                                       else
+                                       {
+                                               values[nresult] = replace;
+                                               isNull = replace_isnull;
+                                       }
+                               }
+                       }
+               }
+
+               if (!skip)
+               {
+                       nulls[nresult] = isNull;
+                       if (isNull)
+                               hasnulls = true;
+                       else
+                       {
+                               /* Update total result size */
+                               nbytes = att_addlength_datum(nbytes, typlen, values[nresult]);
+                               nbytes = att_align_nominal(nbytes, typalign);
+                               /* check for overflow of total request */
+                               if (!AllocSizeIsValid(nbytes))
+                                       ereport(ERROR,
+                                                       (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                                                        errmsg("array size exceeds the maximum allowed (%d)",
+                                                                       (int) MaxAllocSize)));
+                       }
+                       nresult++;
+               }
+
+               /* advance bitmap pointer if any */
+               if (bitmap)
+               {
+                       bitmask <<= 1;
+                       if (bitmask == 0x100)
+                       {
+                               bitmap++;
+                               bitmask = 1;
+                       }
+               }
+       }
+
+       /*
+        * If not changed just return the original array
+        */
+       if (!changed)
+       {
+               pfree(values);
+               pfree(nulls);
+               return array;
+       }
+
+       /* Allocate and initialize the result array */
+       if (hasnulls)
+       {
+               dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nresult);
+               nbytes += dataoffset;
+       }
+       else
+       {
+               dataoffset = 0;                 /* marker for no null bitmap */
+               nbytes += ARR_OVERHEAD_NONULLS(ndim);
+       }
+       result = (ArrayType *) palloc0(nbytes);
+       SET_VARSIZE(result, nbytes);
+       result->ndim = ndim;
+       result->dataoffset = dataoffset;
+       result->elemtype = element_type;
+       memcpy(ARR_DIMS(result), ARR_DIMS(array), 2 * ndim * sizeof(int));
+
+       if (remove)
+       {
+               /* Adjust the result length */
+               ARR_DIMS(result)[0] = nresult;
+       }
+
+       /* Insert data into result array */
+       CopyArrayEls(result,
+                                values, nulls, nresult,
+                                typlen, typbyval, typalign,
+                                false);
+
+       pfree(values);
+       pfree(nulls);
+
+       return result;
+}
+
+/*
+ * Remove any occurrences of an element from an array
+ *
+ * If used on a multi-dimensional array this will raise an error.
+ */
+Datum
+array_remove(PG_FUNCTION_ARGS)
+{
+       ArrayType  *array;
+       Datum           search = PG_GETARG_DATUM(1);
+       bool            search_isnull = PG_ARGISNULL(1);
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+       array = PG_GETARG_ARRAYTYPE_P(0);
+
+       array = array_replace_internal(array,
+                                                                  search, search_isnull,
+                                                                  (Datum) 0, true,
+                                                                  true, PG_GET_COLLATION(),
+                                                                  fcinfo);
+       PG_RETURN_ARRAYTYPE_P(array);
+}
+
+/*
+ * Replace any occurrences of an element in an array
+ */
+Datum
+array_replace(PG_FUNCTION_ARGS)
+{
+       ArrayType  *array;
+       Datum           search = PG_GETARG_DATUM(1);
+       bool            search_isnull = PG_ARGISNULL(1);
+       Datum           replace = PG_GETARG_DATUM(2);
+       bool            replace_isnull = PG_ARGISNULL(2);
+
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+       array = PG_GETARG_ARRAYTYPE_P(0);
+
+       array = array_replace_internal(array,
+                                                                  search, search_isnull,
+                                                                  replace, replace_isnull,
+                                                                  false, PG_GET_COLLATION(),
+                                                                  fcinfo);
+       PG_RETURN_ARRAYTYPE_P(array);
+}
index b665882d832ab96f9c367706fb9d0a84a063c92e..1cf74db762bfd54f5a214e7819dd0326d6d2c3be 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201206171
+#define CATALOG_VERSION_NO     201207111
 
 #endif
index bee7154fd8cc7ff5835cd8e861d17d7bc1ce01bc..4f505cf6fc1df6e2cc73c0bd8e8ef1aef41a0fbe 100644 (file)
@@ -867,6 +867,10 @@ DATA(insert OID = 1286 (  array_fill PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 22
 DESCR("array constructor with value");
 DATA(insert OID = 2331 (  unnest                  PGNSP PGUID 12 1 100 0 0 f f f f t t i 1 0 2283 "2277" _null_ _null_ _null_ _null_ array_unnest _null_ _null_ _null_ ));
 DESCR("expand array to set of rows");
+DATA(insert OID = 3167 (  array_remove    PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2277 "2277 2283" _null_ _null_ _null_ _null_ array_remove _null_ _null_ _null_ ));
+DESCR("remove any occurrences of an element from an array");
+DATA(insert OID = 3168 (  array_replace           PGNSP PGUID 12 1 0 0 0 f f f f f f i 3 0 2277 "2277 2283 2283" _null_ _null_ _null_ _null_ array_replace _null_ _null_ _null_ ));
+DESCR("replace any occurrences of an element in an array");
 DATA(insert OID = 2333 (  array_agg_transfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2283" _null_ _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ ));
 DESCR("aggregate transition function");
 DATA(insert OID = 2334 (  array_agg_finalfn   PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2277 "2281" _null_ _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ ));
index 1da20fefdabe1ae0191761605f9adbed820292fd..7f6aaa8bbb5470f23017c1798a3ea4cf1bad1195 100644 (file)
@@ -211,6 +211,8 @@ extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
 extern Datum array_fill(PG_FUNCTION_ARGS);
 extern Datum array_fill_with_lower_bounds(PG_FUNCTION_ARGS);
 extern Datum array_unnest(PG_FUNCTION_ARGS);
+extern Datum array_remove(PG_FUNCTION_ARGS);
+extern Datum array_replace(PG_FUNCTION_ARGS);
 
 extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
                  int arraytyplen, int elmlen, bool elmbyval, char elmalign,
index 04d186bb14a068693289d389c7bd7559b2d11ea8..051bac92342c477222a9605fe9a006aea9fb1195 100644 (file)
@@ -1542,6 +1542,68 @@ select unnest(array[1,2,3,null,4,null,null,5,6]::text[]);
  6
 (9 rows)
 
+select array_remove(array[1,2,2,3], 2);
+ array_remove 
+--------------
+ {1,3}
+(1 row)
+
+select array_remove(array[1,2,2,3], 5);
+ array_remove 
+--------------
+ {1,2,2,3}
+(1 row)
+
+select array_remove(array[1,NULL,NULL,3], NULL);
+ array_remove 
+--------------
+ {1,3}
+(1 row)
+
+select array_remove(array['A','CC','D','C','RR'], 'RR');
+ array_remove 
+--------------
+ {A,CC,D,C}
+(1 row)
+
+select array_remove('{{1,2,2},{1,4,3}}', 2); -- not allowed
+ERROR:  removing elements from multidimensional arrays is not supported
+select array_replace(array[1,2,5,4],5,3);
+ array_replace 
+---------------
+ {1,2,3,4}
+(1 row)
+
+select array_replace(array[1,2,5,4],5,NULL);
+ array_replace 
+---------------
+ {1,2,NULL,4}
+(1 row)
+
+select array_replace(array[1,2,NULL,4,NULL],NULL,5);
+ array_replace 
+---------------
+ {1,2,5,4,5}
+(1 row)
+
+select array_replace(array['A','B','DD','B'],'B','CC');
+ array_replace 
+---------------
+ {A,CC,DD,CC}
+(1 row)
+
+select array_replace(array[1,NULL,3],NULL,NULL);
+ array_replace 
+---------------
+ {1,NULL,3}
+(1 row)
+
+select array_replace(array['AB',NULL,'CDE'],NULL,'12');
+ array_replace 
+---------------
+ {AB,12,CDE}
+(1 row)
+
 -- Insert/update on a column that is array of composite
 create temp table t1 (f1 int8_tbl[]);
 insert into t1 (f1[5].q1) values(42);
index 294b44ee086f415b396c67942e2dd8ec8443453a..04e97254e1456780532e7e52f8c6988a9616ea67 100644 (file)
@@ -432,6 +432,17 @@ select unnest(array[1,2,3,4.5]::float8[]);
 select unnest(array[1,2,3,4.5]::numeric[]);
 select unnest(array[1,2,3,null,4,null,null,5,6]);
 select unnest(array[1,2,3,null,4,null,null,5,6]::text[]);
+select array_remove(array[1,2,2,3], 2);
+select array_remove(array[1,2,2,3], 5);
+select array_remove(array[1,NULL,NULL,3], NULL);
+select array_remove(array['A','CC','D','C','RR'], 'RR');
+select array_remove('{{1,2,2},{1,4,3}}', 2); -- not allowed
+select array_replace(array[1,2,5,4],5,3);
+select array_replace(array[1,2,5,4],5,NULL);
+select array_replace(array[1,2,NULL,4,NULL],NULL,5);
+select array_replace(array['A','B','DD','B'],'B','CC');
+select array_replace(array[1,NULL,3],NULL,NULL);
+select array_replace(array['AB',NULL,'CDE'],NULL,'12');
 
 -- Insert/update on a column that is array of composite