Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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)