Skip to content

Commit 84a4256

Browse files
committed
Add array_remove() and array_replace() functions.
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
1 parent f995125 commit 84a4256

File tree

7 files changed

+415
-1
lines changed

7 files changed

+415
-1
lines changed

doc/src/sgml/func.sgml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10316,6 +10316,12 @@ SELECT NULLIF(value, '(none)') ...
1031610316
<indexterm>
1031710317
<primary>array_prepend</primary>
1031810318
</indexterm>
10319+
<indexterm>
10320+
<primary>array_remove</primary>
10321+
</indexterm>
10322+
<indexterm>
10323+
<primary>array_replace</primary>
10324+
</indexterm>
1031910325
<indexterm>
1032010326
<primary>array_to_string</primary>
1032110327
</indexterm>
@@ -10432,6 +10438,29 @@ SELECT NULLIF(value, '(none)') ...
1043210438
<entry><literal>array_prepend(1, ARRAY[2,3])</literal></entry>
1043310439
<entry><literal>{1,2,3}</literal></entry>
1043410440
</row>
10441+
<row>
10442+
<entry>
10443+
<literal>
10444+
<function>array_remove</function>(<type>anyarray</type>, <type>anyelement</type>)
10445+
</literal>
10446+
</entry>
10447+
<entry><type>anyarray</type></entry>
10448+
<entry>remove all elements equal to the given value from the array
10449+
(array must be one-dimensional)</entry>
10450+
<entry><literal>array_remove(ARRAY[1,2,3,2], 2)</literal></entry>
10451+
<entry><literal>{1,3}</literal></entry>
10452+
</row>
10453+
<row>
10454+
<entry>
10455+
<literal>
10456+
<function>array_replace</function>(<type>anyarray</type>, <type>anyelement</type>, <type>anyelement</type>)
10457+
</literal>
10458+
</entry>
10459+
<entry><type>anyarray</type></entry>
10460+
<entry>replace each array element equal to the given value with a new value</entry>
10461+
<entry><literal>array_replace(ARRAY[1,2,5,4], 5, 3)</literal></entry>
10462+
<entry><literal>{1,2,3,4}</literal></entry>
10463+
</row>
1043510464
<row>
1043610465
<entry>
1043710466
<literal>

src/backend/utils/adt/arrayfuncs.c

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ static ArrayType *create_array_envelope(int ndims, int *dimv, int *lbv, int nbyt
124124
static ArrayType *array_fill_internal(ArrayType *dims, ArrayType *lbs,
125125
Datum value, bool isnull, Oid elmtype,
126126
FunctionCallInfo fcinfo);
127+
static ArrayType *array_replace_internal(ArrayType *array,
128+
Datum search, bool search_isnull,
129+
Datum replace, bool replace_isnull,
130+
bool remove, Oid collation,
131+
FunctionCallInfo fcinfo);
127132

128133

129134
/*
@@ -5174,3 +5179,304 @@ array_unnest(PG_FUNCTION_ARGS)
51745179
SRF_RETURN_DONE(funcctx);
51755180
}
51765181
}
5182+
5183+
5184+
/*
5185+
* array_replace/array_remove support
5186+
*
5187+
* Find all array entries matching (not distinct from) search/search_isnull,
5188+
* and delete them if remove is true, else replace them with
5189+
* replace/replace_isnull. Comparisons are done using the specified
5190+
* collation. fcinfo is passed only for caching purposes.
5191+
*/
5192+
static ArrayType *
5193+
array_replace_internal(ArrayType *array,
5194+
Datum search, bool search_isnull,
5195+
Datum replace, bool replace_isnull,
5196+
bool remove, Oid collation,
5197+
FunctionCallInfo fcinfo)
5198+
{
5199+
ArrayType *result;
5200+
Oid element_type;
5201+
Datum *values;
5202+
bool *nulls;
5203+
int *dim;
5204+
int ndim;
5205+
int nitems,
5206+
nresult;
5207+
int i;
5208+
int32 nbytes = 0;
5209+
int32 dataoffset;
5210+
bool hasnulls;
5211+
int typlen;
5212+
bool typbyval;
5213+
char typalign;
5214+
char *arraydataptr;
5215+
bits8 *bitmap;
5216+
int bitmask;
5217+
bool changed = false;
5218+
TypeCacheEntry *typentry;
5219+
FunctionCallInfoData locfcinfo;
5220+
5221+
element_type = ARR_ELEMTYPE(array);
5222+
ndim = ARR_NDIM(array);
5223+
dim = ARR_DIMS(array);
5224+
nitems = ArrayGetNItems(ndim, dim);
5225+
5226+
/* Return input array unmodified if it is empty */
5227+
if (nitems <= 0)
5228+
return array;
5229+
5230+
/*
5231+
* We can't remove elements from multi-dimensional arrays, since the
5232+
* result might not be rectangular.
5233+
*/
5234+
if (remove && ndim > 1)
5235+
ereport(ERROR,
5236+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5237+
errmsg("removing elements from multidimensional arrays is not supported")));
5238+
5239+
/*
5240+
* We arrange to look up the equality function only once per series of
5241+
* calls, assuming the element type doesn't change underneath us.
5242+
*/
5243+
typentry = (TypeCacheEntry *) fcinfo->flinfo->fn_extra;
5244+
if (typentry == NULL ||
5245+
typentry->type_id != element_type)
5246+
{
5247+
typentry = lookup_type_cache(element_type,
5248+
TYPECACHE_EQ_OPR_FINFO);
5249+
if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
5250+
ereport(ERROR,
5251+
(errcode(ERRCODE_UNDEFINED_FUNCTION),
5252+
errmsg("could not identify an equality operator for type %s",
5253+
format_type_be(element_type))));
5254+
fcinfo->flinfo->fn_extra = (void *) typentry;
5255+
}
5256+
typlen = typentry->typlen;
5257+
typbyval = typentry->typbyval;
5258+
typalign = typentry->typalign;
5259+
5260+
/*
5261+
* Detoast values if they are toasted. The replacement value must be
5262+
* detoasted for insertion into the result array, while detoasting the
5263+
* search value only once saves cycles.
5264+
*/
5265+
if (typlen == -1)
5266+
{
5267+
if (!search_isnull)
5268+
search = PointerGetDatum(PG_DETOAST_DATUM(search));
5269+
if (!replace_isnull)
5270+
replace = PointerGetDatum(PG_DETOAST_DATUM(replace));
5271+
}
5272+
5273+
/* Prepare to apply the comparison operator */
5274+
InitFunctionCallInfoData(locfcinfo, &typentry->eq_opr_finfo, 2,
5275+
collation, NULL, NULL);
5276+
5277+
/* Allocate temporary arrays for new values */
5278+
values = (Datum *) palloc(nitems * sizeof(Datum));
5279+
nulls = (bool *) palloc(nitems * sizeof(bool));
5280+
5281+
/* Loop over source data */
5282+
arraydataptr = ARR_DATA_PTR(array);
5283+
bitmap = ARR_NULLBITMAP(array);
5284+
bitmask = 1;
5285+
hasnulls = false;
5286+
nresult = 0;
5287+
5288+
for (i = 0; i < nitems; i++)
5289+
{
5290+
Datum elt;
5291+
bool isNull;
5292+
bool oprresult;
5293+
bool skip = false;
5294+
5295+
/* Get source element, checking for NULL */
5296+
if (bitmap && (*bitmap & bitmask) == 0)
5297+
{
5298+
isNull = true;
5299+
/* If searching for NULL, we have a match */
5300+
if (search_isnull)
5301+
{
5302+
if (remove)
5303+
{
5304+
skip = true;
5305+
changed = true;
5306+
}
5307+
else if (!replace_isnull)
5308+
{
5309+
values[nresult] = replace;
5310+
isNull = false;
5311+
changed = true;
5312+
}
5313+
}
5314+
}
5315+
else
5316+
{
5317+
isNull = false;
5318+
elt = fetch_att(arraydataptr, typbyval, typlen);
5319+
arraydataptr = att_addlength_datum(arraydataptr, typlen, elt);
5320+
arraydataptr = (char *) att_align_nominal(arraydataptr, typalign);
5321+
5322+
if (search_isnull)
5323+
{
5324+
/* no match possible, keep element */
5325+
values[nresult] = elt;
5326+
}
5327+
else
5328+
{
5329+
/*
5330+
* Apply the operator to the element pair
5331+
*/
5332+
locfcinfo.arg[0] = elt;
5333+
locfcinfo.arg[1] = search;
5334+
locfcinfo.argnull[0] = false;
5335+
locfcinfo.argnull[1] = false;
5336+
locfcinfo.isnull = false;
5337+
oprresult = DatumGetBool(FunctionCallInvoke(&locfcinfo));
5338+
if (!oprresult)
5339+
{
5340+
/* no match, keep element */
5341+
values[nresult] = elt;
5342+
}
5343+
else
5344+
{
5345+
/* match, so replace or delete */
5346+
changed = true;
5347+
if (remove)
5348+
skip = true;
5349+
else
5350+
{
5351+
values[nresult] = replace;
5352+
isNull = replace_isnull;
5353+
}
5354+
}
5355+
}
5356+
}
5357+
5358+
if (!skip)
5359+
{
5360+
nulls[nresult] = isNull;
5361+
if (isNull)
5362+
hasnulls = true;
5363+
else
5364+
{
5365+
/* Update total result size */
5366+
nbytes = att_addlength_datum(nbytes, typlen, values[nresult]);
5367+
nbytes = att_align_nominal(nbytes, typalign);
5368+
/* check for overflow of total request */
5369+
if (!AllocSizeIsValid(nbytes))
5370+
ereport(ERROR,
5371+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
5372+
errmsg("array size exceeds the maximum allowed (%d)",
5373+
(int) MaxAllocSize)));
5374+
}
5375+
nresult++;
5376+
}
5377+
5378+
/* advance bitmap pointer if any */
5379+
if (bitmap)
5380+
{
5381+
bitmask <<= 1;
5382+
if (bitmask == 0x100)
5383+
{
5384+
bitmap++;
5385+
bitmask = 1;
5386+
}
5387+
}
5388+
}
5389+
5390+
/*
5391+
* If not changed just return the original array
5392+
*/
5393+
if (!changed)
5394+
{
5395+
pfree(values);
5396+
pfree(nulls);
5397+
return array;
5398+
}
5399+
5400+
/* Allocate and initialize the result array */
5401+
if (hasnulls)
5402+
{
5403+
dataoffset = ARR_OVERHEAD_WITHNULLS(ndim, nresult);
5404+
nbytes += dataoffset;
5405+
}
5406+
else
5407+
{
5408+
dataoffset = 0; /* marker for no null bitmap */
5409+
nbytes += ARR_OVERHEAD_NONULLS(ndim);
5410+
}
5411+
result = (ArrayType *) palloc0(nbytes);
5412+
SET_VARSIZE(result, nbytes);
5413+
result->ndim = ndim;
5414+
result->dataoffset = dataoffset;
5415+
result->elemtype = element_type;
5416+
memcpy(ARR_DIMS(result), ARR_DIMS(array), 2 * ndim * sizeof(int));
5417+
5418+
if (remove)
5419+
{
5420+
/* Adjust the result length */
5421+
ARR_DIMS(result)[0] = nresult;
5422+
}
5423+
5424+
/* Insert data into result array */
5425+
CopyArrayEls(result,
5426+
values, nulls, nresult,
5427+
typlen, typbyval, typalign,
5428+
false);
5429+
5430+
pfree(values);
5431+
pfree(nulls);
5432+
5433+
return result;
5434+
}
5435+
5436+
/*
5437+
* Remove any occurrences of an element from an array
5438+
*
5439+
* If used on a multi-dimensional array this will raise an error.
5440+
*/
5441+
Datum
5442+
array_remove(PG_FUNCTION_ARGS)
5443+
{
5444+
ArrayType *array;
5445+
Datum search = PG_GETARG_DATUM(1);
5446+
bool search_isnull = PG_ARGISNULL(1);
5447+
5448+
if (PG_ARGISNULL(0))
5449+
PG_RETURN_NULL();
5450+
array = PG_GETARG_ARRAYTYPE_P(0);
5451+
5452+
array = array_replace_internal(array,
5453+
search, search_isnull,
5454+
(Datum) 0, true,
5455+
true, PG_GET_COLLATION(),
5456+
fcinfo);
5457+
PG_RETURN_ARRAYTYPE_P(array);
5458+
}
5459+
5460+
/*
5461+
* Replace any occurrences of an element in an array
5462+
*/
5463+
Datum
5464+
array_replace(PG_FUNCTION_ARGS)
5465+
{
5466+
ArrayType *array;
5467+
Datum search = PG_GETARG_DATUM(1);
5468+
bool search_isnull = PG_ARGISNULL(1);
5469+
Datum replace = PG_GETARG_DATUM(2);
5470+
bool replace_isnull = PG_ARGISNULL(2);
5471+
5472+
if (PG_ARGISNULL(0))
5473+
PG_RETURN_NULL();
5474+
array = PG_GETARG_ARRAYTYPE_P(0);
5475+
5476+
array = array_replace_internal(array,
5477+
search, search_isnull,
5478+
replace, replace_isnull,
5479+
false, PG_GET_COLLATION(),
5480+
fcinfo);
5481+
PG_RETURN_ARRAYTYPE_P(array);
5482+
}

src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@
5353
*/
5454

5555
/* yyyymmddN */
56-
#define CATALOG_VERSION_NO 201206171
56+
#define CATALOG_VERSION_NO 201207111
5757

5858
#endif

src/include/catalog/pg_proc.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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
867867
DESCR("array constructor with value");
868868
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_ ));
869869
DESCR("expand array to set of rows");
870+
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_ ));
871+
DESCR("remove any occurrences of an element from an array");
872+
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_ ));
873+
DESCR("replace any occurrences of an element in an array");
870874
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_ ));
871875
DESCR("aggregate transition function");
872876
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_ ));

src/include/utils/array.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ extern Datum generate_subscripts_nodir(PG_FUNCTION_ARGS);
211211
extern Datum array_fill(PG_FUNCTION_ARGS);
212212
extern Datum array_fill_with_lower_bounds(PG_FUNCTION_ARGS);
213213
extern Datum array_unnest(PG_FUNCTION_ARGS);
214+
extern Datum array_remove(PG_FUNCTION_ARGS);
215+
extern Datum array_replace(PG_FUNCTION_ARGS);
214216

215217
extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
216218
int arraytyplen, int elmlen, bool elmbyval, char elmalign,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy