From c889ebce0aa5f848d680547e3af0aad8b9e577a7 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 14 Nov 2008 00:51:47 +0000 Subject: [PATCH] Implement the basic form of UNNEST, ie unnest(anyarray) returns setof anyelement. This lacks the WITH ORDINALITY option, as well as the multiple input arrays option added in the most recent SQL specs. But it's still a pretty useful subset of the spec's functionality, and it is enough to allow obsoleting contrib/intagg. --- doc/src/sgml/func.sgml | 13 +++- src/backend/utils/adt/arrayfuncs.c | 106 ++++++++++++++++++++++++++- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_proc.h | 4 +- src/include/utils/array.h | 3 +- src/test/regress/expected/arrays.out | 62 ++++++++++++++++ src/test/regress/sql/arrays.sql | 7 ++ 7 files changed, 193 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index aee74366b01..67500340b0f 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -9482,6 +9482,17 @@ SELECT NULLIF(value, '(none)') ... string_to_array('xx~^~yy~^~zz', '~^~') {xx,yy,zz} + + + + unnest(anyarray) + + + setof anyelement + expand an array to a set of rows + unnest(ARRAY[1,2]) + 12 (2 rows) + diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c index 9d2b036897b..4580040d697 100644 --- a/src/backend/utils/adt/arrayfuncs.c +++ b/src/backend/utils/adt/arrayfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.149 2008/11/12 13:09:27 petere Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/arrayfuncs.c,v 1.150 2008/11/14 00:51:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -4635,3 +4635,107 @@ array_fill_internal(ArrayType *dims, ArrayType *lbs, return result; } + + +/* + * UNNEST + */ +Datum +array_unnest(PG_FUNCTION_ARGS) +{ + typedef struct + { + ArrayType *arr; + int nextelem; + int numelems; + char *elemdataptr; /* this moves with nextelem */ + bits8 *arraynullsptr; /* this does not */ + int16 elmlen; + bool elmbyval; + char elmalign; + } array_unnest_fctx; + + FuncCallContext *funcctx; + array_unnest_fctx *fctx; + MemoryContext oldcontext; + + /* stuff done only on the first call of the function */ + if (SRF_IS_FIRSTCALL()) + { + ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0); + + /* create a function context for cross-call persistence */ + funcctx = SRF_FIRSTCALL_INIT(); + + /* + * switch to memory context appropriate for multiple function calls + */ + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + /* allocate memory for user context */ + fctx = (array_unnest_fctx *) palloc(sizeof(array_unnest_fctx)); + + /* + * Initialize state. Note we assume that the originally passed + * array will stick around for the whole call series. + */ + fctx->arr = arr; + fctx->nextelem = 0; + fctx->numelems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)); + + fctx->elemdataptr = ARR_DATA_PTR(arr); + fctx->arraynullsptr = ARR_NULLBITMAP(arr); + + get_typlenbyvalalign(ARR_ELEMTYPE(arr), + &fctx->elmlen, + &fctx->elmbyval, + &fctx->elmalign); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + /* stuff done on every call of the function */ + funcctx = SRF_PERCALL_SETUP(); + fctx = funcctx->user_fctx; + + if (fctx->nextelem < fctx->numelems) + { + int offset = fctx->nextelem++; + Datum elem; + + /* + * Check for NULL array element + */ + if (array_get_isnull(fctx->arraynullsptr, offset)) + { + fcinfo->isnull = true; + elem = (Datum) 0; + /* elemdataptr does not move */ + } + else + { + /* + * OK, get the element + */ + char *ptr = fctx->elemdataptr; + + fcinfo->isnull = false; + elem = ArrayCast(ptr, fctx->elmbyval, fctx->elmlen); + + /* + * Advance elemdataptr over it + */ + ptr = att_addlength_pointer(ptr, fctx->elmlen, ptr); + ptr = (char *) att_align_nominal(ptr, fctx->elmalign); + fctx->elemdataptr = ptr; + } + + SRF_RETURN_NEXT(funcctx, elem); + } + else + { + /* do when there is no more left */ + SRF_RETURN_DONE(funcctx); + } +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 1f3d9c6485c..dd1bfbaf45f 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.505 2008/11/13 15:59:50 petere Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.506 2008/11/14 00:51:46 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200811131 +#define CATALOG_VERSION_NO 200811132 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 5c01d1b3701..56b7f6786aa 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.527 2008/11/13 15:59:50 petere Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.528 2008/11/14 00:51:46 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -1022,6 +1022,8 @@ DATA(insert OID = 1193 ( array_fill PGNSP PGUID 12 1 0 0 f f f f i 2 2277 "2283 DESCR("array constructor with value"); DATA(insert OID = 1286 ( array_fill PGNSP PGUID 12 1 0 0 f f f f i 3 2277 "2283 1007 1007" _null_ _null_ _null_ array_fill_with_lower_bounds _null_ _null_ _null_ )); DESCR("array constructor with value"); +DATA(insert OID = 2331 ( unnest PGNSP PGUID 12 1 100 0 f f t t i 1 2283 "2277" _null_ _null_ _null_ array_unnest _null_ _null_ _null_ )); +DESCR("expand array to set of rows"); DATA(insert OID = 2333 ( array_agg_transfn PGNSP PGUID 12 1 0 0 f f f f i 2 2281 "2281 2283" _null_ _null_ _null_ array_agg_transfn _null_ _null_ _null_ )); DESCR("array_agg transition function"); DATA(insert OID = 2334 ( array_agg_finalfn PGNSP PGUID 12 1 0 0 f f f f i 1 2277 "2281" _null_ _null_ _null_ array_agg_finalfn _null_ _null_ _null_ )); diff --git a/src/include/utils/array.h b/src/include/utils/array.h index 8b6ef08b276..8a7f10451f0 100644 --- a/src/include/utils/array.h +++ b/src/include/utils/array.h @@ -49,7 +49,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/array.h,v 1.71 2008/11/13 15:59:50 petere Exp $ + * $PostgreSQL: pgsql/src/include/utils/array.h,v 1.72 2008/11/14 00:51:47 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -206,6 +206,7 @@ extern Datum generate_subscripts(PG_FUNCTION_ARGS); 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_ref(ArrayType *array, int nSubscripts, int *indx, int arraytyplen, int elmlen, bool elmbyval, char elmalign, diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 1e990aff732..aecc74c5c4b 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -1161,3 +1161,65 @@ select array_agg(unique1) from tenk1 where unique1 < -15; (1 row) +select unnest(array[1,2,3]); + unnest +-------- + 1 + 2 + 3 +(3 rows) + +select * from unnest(array[1,2,3]); + unnest +-------- + 1 + 2 + 3 +(3 rows) + +select unnest(array[1,2,3,4.5]::float8[]); + unnest +-------- + 1 + 2 + 3 + 4.5 +(4 rows) + +select unnest(array[1,2,3,4.5]::numeric[]); + unnest +-------- + 1 + 2 + 3 + 4.5 +(4 rows) + +select unnest(array[1,2,3,null,4,null,null,5,6]); + unnest +-------- + 1 + 2 + 3 + + 4 + + + 5 + 6 +(9 rows) + +select unnest(array[1,2,3,null,4,null,null,5,6]::text[]); + unnest +-------- + 1 + 2 + 3 + + 4 + + + 5 + 6 +(9 rows) + diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 586f65c2dd6..fc72f29f602 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -402,3 +402,10 @@ select array_agg(nullif(ten, 4)) from tenk1 where unique1 < 15; select cardinality(array_agg(unique1)) from tenk1 where unique1 < 15; select array_agg(unique1) from (select * from tenk1 order by unique1 asc) as tab where unique1 < 15; select array_agg(unique1) from tenk1 where unique1 < -15; + +select unnest(array[1,2,3]); +select * from unnest(array[1,2,3]); +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[]);