Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 6e02755

Browse files
committed
Add FOREACH IN ARRAY looping to plpgsql.
(I'm not entirely sure that we've finished bikeshedding the syntax details, but the functionality seems OK.) Pavel Stehule, reviewed by Stephen Frost and Tom Lane
1 parent 4695da5 commit 6e02755

File tree

10 files changed

+899
-15
lines changed

10 files changed

+899
-15
lines changed

doc/src/sgml/plpgsql.sgml

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,7 +1263,7 @@ EXECUTE 'UPDATE tbl SET '
12631263
<programlisting>
12641264
EXECUTE format('UPDATE tbl SET %I = %L WHERE key = %L', colname, newvalue, keyvalue);
12651265
</programlisting>
1266-
The <function>format</function> function can be used in conjunction with
1266+
The <function>format</function> function can be used in conjunction with
12671267
the <literal>USING</literal> clause:
12681268
<programlisting>
12691269
EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
@@ -1356,19 +1356,15 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
13561356
true if it successfully repositions the cursor, false otherwise.
13571357
</para>
13581358
</listitem>
1359-
13601359
<listitem>
13611360
<para>
1362-
A <command>FOR</> statement sets <literal>FOUND</literal> true
1363-
if it iterates one or more times, else false. This applies to
1364-
all four variants of the <command>FOR</> statement (integer
1365-
<command>FOR</> loops, record-set <command>FOR</> loops,
1366-
dynamic record-set <command>FOR</> loops, and cursor
1367-
<command>FOR</> loops).
1361+
A <command>FOR</> or <command>FOREACH</> statement sets
1362+
<literal>FOUND</literal> true
1363+
if it iterates one or more times, else false.
13681364
<literal>FOUND</literal> is set this way when the
1369-
<command>FOR</> loop exits; inside the execution of the loop,
1365+
loop exits; inside the execution of the loop,
13701366
<literal>FOUND</literal> is not modified by the
1371-
<command>FOR</> statement, although it might be changed by the
1367+
loop statement, although it might be changed by the
13721368
execution of other statements within the loop body.
13731369
</para>
13741370
</listitem>
@@ -1910,9 +1906,9 @@ END CASE;
19101906

19111907
<para>
19121908
With the <literal>LOOP</>, <literal>EXIT</>,
1913-
<literal>CONTINUE</>, <literal>WHILE</>, and <literal>FOR</>
1914-
statements, you can arrange for your <application>PL/pgSQL</>
1915-
function to repeat a series of commands.
1909+
<literal>CONTINUE</>, <literal>WHILE</>, <literal>FOR</>,
1910+
and <literal>FOREACH</> statements, you can arrange for your
1911+
<application>PL/pgSQL</> function to repeat a series of commands.
19161912
</para>
19171913

19181914
<sect3>
@@ -2238,6 +2234,90 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
22382234
</para>
22392235
</sect2>
22402236

2237+
<sect2 id="plpgsql-foreach-array">
2238+
<title>Looping Through Arrays</title>
2239+
2240+
<para>
2241+
The <literal>FOREACH</> loop is much like a <literal>FOR</> loop,
2242+
but instead of iterating through the rows returned by a SQL query,
2243+
it iterates through the elements of an array value.
2244+
(In general, <literal>FOREACH</> is meant for looping through
2245+
components of a composite-valued expression; variants for looping
2246+
through composites besides arrays may be added in future.)
2247+
The <literal>FOREACH</> statement to loop over an array is:
2248+
2249+
<synopsis>
2250+
<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
2251+
FOREACH <replaceable>target</replaceable> <optional> SLICE <replaceable>number</replaceable> </optional> IN ARRAY <replaceable>expression</replaceable> LOOP
2252+
<replaceable>statements</replaceable>
2253+
END LOOP <optional> <replaceable>label</replaceable> </optional>;
2254+
</synopsis>
2255+
</para>
2256+
2257+
<para>
2258+
Without <literal>SLICE</>, or if <literal>SLICE 0</> is specified,
2259+
the loop iterates through individual elements of the array produced
2260+
by evaluating the <replaceable>expression</replaceable>.
2261+
The <replaceable>target</replaceable> variable is assigned each
2262+
element value in sequence, and the loop body is executed for each element.
2263+
Here is an example of looping through the elements of an integer
2264+
array:
2265+
2266+
<programlisting>
2267+
CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
2268+
DECLARE
2269+
s int8 := 0;
2270+
x int;
2271+
BEGIN
2272+
FOREACH x IN ARRAY $1
2273+
LOOP
2274+
s := s + x;
2275+
END LOOP;
2276+
RETURN s;
2277+
END;
2278+
$$ LANGUAGE plpgsql;
2279+
</programlisting>
2280+
2281+
The elements are visited in storage order, regardless of the number of
2282+
array dimensions. Although the <replaceable>target</replaceable> is
2283+
usually just a single variable, it can be a list of variables when
2284+
looping through an array of composite values (records). In that case,
2285+
for each array element, the variables are assigned from successive
2286+
columns of the composite value.
2287+
</para>
2288+
2289+
<para>
2290+
With a positive <literal>SLICE</> value, <literal>FOREACH</>
2291+
iterates through slices of the array rather than single elements.
2292+
The <literal>SLICE</> value must be an integer constant not larger
2293+
than the number of dimensions of the array. The
2294+
<replaceable>target</replaceable> variable must be an array,
2295+
and it receives successive slices of the array value, where each slice
2296+
is of the number of dimensions specified by <literal>SLICE</>.
2297+
Here is an example of iterating through one-dimensional slices:
2298+
2299+
<programlisting>
2300+
CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
2301+
DECLARE
2302+
x int[];
2303+
BEGIN
2304+
FOREACH x SLICE 1 IN ARRAY $1
2305+
LOOP
2306+
RAISE NOTICE 'row = %', x;
2307+
END LOOP;
2308+
END;
2309+
$$ LANGUAGE plpgsql;
2310+
2311+
SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);
2312+
2313+
NOTICE: row = {1,2,3}
2314+
NOTICE: row = {4,5,6}
2315+
NOTICE: row = {7,8,9}
2316+
NOTICE: row = {10,11,12}
2317+
</programlisting>
2318+
</para>
2319+
</sect2>
2320+
22412321
<sect2 id="plpgsql-error-trapping">
22422322
<title>Trapping Errors</title>
22432323

src/backend/utils/adt/arrayfuncs.c

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ typedef enum
5050
ARRAY_LEVEL_DELIMITED
5151
} ArrayParseState;
5252

53+
/* Working state for array_iterate() */
54+
typedef struct ArrayIteratorData
55+
{
56+
/* basic info about the array, set up during array_create_iterator() */
57+
ArrayType *arr; /* array we're iterating through */
58+
bits8 *nullbitmap; /* its null bitmap, if any */
59+
int nitems; /* total number of elements in array */
60+
int16 typlen; /* element type's length */
61+
bool typbyval; /* element type's byval property */
62+
char typalign; /* element type's align property */
63+
64+
/* information about the requested slice size */
65+
int slice_ndim; /* slice dimension, or 0 if not slicing */
66+
int slice_len; /* number of elements per slice */
67+
int *slice_dims; /* slice dims array */
68+
int *slice_lbound; /* slice lbound array */
69+
Datum *slice_values; /* workspace of length slice_len */
70+
bool *slice_nulls; /* workspace of length slice_len */
71+
72+
/* current position information, updated on each iteration */
73+
char *data_ptr; /* our current position in the array */
74+
int current_item; /* the item # we're at in the array */
75+
} ArrayIteratorData;
76+
5377
static bool array_isspace(char ch);
5478
static int ArrayCount(const char *str, int *dim, char typdelim);
5579
static void ReadArrayStr(char *arrayStr, const char *origStr,
@@ -3833,6 +3857,188 @@ arraycontained(PG_FUNCTION_ARGS)
38333857
}
38343858

38353859

3860+
/*-----------------------------------------------------------------------------
3861+
* Array iteration functions
3862+
* These functions are used to iterate efficiently through arrays
3863+
*-----------------------------------------------------------------------------
3864+
*/
3865+
3866+
/*
3867+
* array_create_iterator --- set up to iterate through an array
3868+
*
3869+
* If slice_ndim is zero, we will iterate element-by-element; the returned
3870+
* datums are of the array's element type.
3871+
*
3872+
* If slice_ndim is 1..ARR_NDIM(arr), we will iterate by slices: the
3873+
* returned datums are of the same array type as 'arr', but of size
3874+
* equal to the rightmost N dimensions of 'arr'.
3875+
*
3876+
* The passed-in array must remain valid for the lifetime of the iterator.
3877+
*/
3878+
ArrayIterator
3879+
array_create_iterator(ArrayType *arr, int slice_ndim)
3880+
{
3881+
ArrayIterator iterator = palloc0(sizeof(ArrayIteratorData));
3882+
3883+
/*
3884+
* Sanity-check inputs --- caller should have got this right already
3885+
*/
3886+
Assert(PointerIsValid(arr));
3887+
if (slice_ndim < 0 || slice_ndim > ARR_NDIM(arr))
3888+
elog(ERROR, "invalid arguments to array_create_iterator");
3889+
3890+
/*
3891+
* Remember basic info about the array and its element type
3892+
*/
3893+
iterator->arr = arr;
3894+
iterator->nullbitmap = ARR_NULLBITMAP(arr);
3895+
iterator->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
3896+
get_typlenbyvalalign(ARR_ELEMTYPE(arr),
3897+
&iterator->typlen,
3898+
&iterator->typbyval,
3899+
&iterator->typalign);
3900+
3901+
/*
3902+
* Remember the slicing parameters.
3903+
*/
3904+
iterator->slice_ndim = slice_ndim;
3905+
3906+
if (slice_ndim > 0)
3907+
{
3908+
/*
3909+
* Get pointers into the array's dims and lbound arrays to represent
3910+
* the dims/lbound arrays of a slice. These are the same as the
3911+
* rightmost N dimensions of the array.
3912+
*/
3913+
iterator->slice_dims = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim;
3914+
iterator->slice_lbound = ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim;
3915+
3916+
/*
3917+
* Compute number of elements in a slice.
3918+
*/
3919+
iterator->slice_len = ArrayGetNItems(slice_ndim,
3920+
iterator->slice_dims);
3921+
3922+
/*
3923+
* Create workspace for building sub-arrays.
3924+
*/
3925+
iterator->slice_values = (Datum *)
3926+
palloc(iterator->slice_len * sizeof(Datum));
3927+
iterator->slice_nulls = (bool *)
3928+
palloc(iterator->slice_len * sizeof(bool));
3929+
}
3930+
3931+
/*
3932+
* Initialize our data pointer and linear element number. These will
3933+
* advance through the array during array_iterate().
3934+
*/
3935+
iterator->data_ptr = ARR_DATA_PTR(arr);
3936+
iterator->current_item = 0;
3937+
3938+
return iterator;
3939+
}
3940+
3941+
/*
3942+
* Iterate through the array referenced by 'iterator'.
3943+
*
3944+
* As long as there is another element (or slice), return it into
3945+
* *value / *isnull, and return true. Return false when no more data.
3946+
*/
3947+
bool
3948+
array_iterate(ArrayIterator iterator, Datum *value, bool *isnull)
3949+
{
3950+
/* Done if we have reached the end of the array */
3951+
if (iterator->current_item >= iterator->nitems)
3952+
return false;
3953+
3954+
if (iterator->slice_ndim == 0)
3955+
{
3956+
/*
3957+
* Scalar case: return one element.
3958+
*/
3959+
if (array_get_isnull(iterator->nullbitmap, iterator->current_item++))
3960+
{
3961+
*isnull = true;
3962+
*value = (Datum) 0;
3963+
}
3964+
else
3965+
{
3966+
/* non-NULL, so fetch the individual Datum to return */
3967+
char *p = iterator->data_ptr;
3968+
3969+
*isnull = false;
3970+
*value = fetch_att(p, iterator->typbyval, iterator->typlen);
3971+
3972+
/* Move our data pointer forward to the next element */
3973+
p = att_addlength_pointer(p, iterator->typlen, p);
3974+
p = (char *) att_align_nominal(p, iterator->typalign);
3975+
iterator->data_ptr = p;
3976+
}
3977+
}
3978+
else
3979+
{
3980+
/*
3981+
* Slice case: build and return an array of the requested size.
3982+
*/
3983+
ArrayType *result;
3984+
Datum *values = iterator->slice_values;
3985+
bool *nulls = iterator->slice_nulls;
3986+
char *p = iterator->data_ptr;
3987+
int i;
3988+
3989+
for (i = 0; i < iterator->slice_len; i++)
3990+
{
3991+
if (array_get_isnull(iterator->nullbitmap,
3992+
iterator->current_item++))
3993+
{
3994+
nulls[i] = true;
3995+
values[i] = (Datum) 0;
3996+
}
3997+
else
3998+
{
3999+
nulls[i] = false;
4000+
values[i] = fetch_att(p, iterator->typbyval, iterator->typlen);
4001+
4002+
/* Move our data pointer forward to the next element */
4003+
p = att_addlength_pointer(p, iterator->typlen, p);
4004+
p = (char *) att_align_nominal(p, iterator->typalign);
4005+
}
4006+
}
4007+
4008+
iterator->data_ptr = p;
4009+
4010+
result = construct_md_array(values,
4011+
nulls,
4012+
iterator->slice_ndim,
4013+
iterator->slice_dims,
4014+
iterator->slice_lbound,
4015+
ARR_ELEMTYPE(iterator->arr),
4016+
iterator->typlen,
4017+
iterator->typbyval,
4018+
iterator->typalign);
4019+
4020+
*isnull = false;
4021+
*value = PointerGetDatum(result);
4022+
}
4023+
4024+
return true;
4025+
}
4026+
4027+
/*
4028+
* Release an ArrayIterator data structure
4029+
*/
4030+
void
4031+
array_free_iterator(ArrayIterator iterator)
4032+
{
4033+
if (iterator->slice_ndim > 0)
4034+
{
4035+
pfree(iterator->slice_values);
4036+
pfree(iterator->slice_nulls);
4037+
}
4038+
pfree(iterator);
4039+
}
4040+
4041+
38364042
/***************************************************************************/
38374043
/******************| Support Routines |*****************/
38384044
/***************************************************************************/

src/include/utils/array.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ typedef struct ArrayMapState
114114
ArrayMetaState ret_extra;
115115
} ArrayMapState;
116116

117+
/* ArrayIteratorData is private in arrayfuncs.c */
118+
typedef struct ArrayIteratorData *ArrayIterator;
119+
117120
/*
118121
* fmgr macros for array objects
119122
*/
@@ -254,6 +257,10 @@ extern Datum makeArrayResult(ArrayBuildState *astate,
254257
extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
255258
int *dims, int *lbs, MemoryContext rcontext, bool release);
256259

260+
extern ArrayIterator array_create_iterator(ArrayType *arr, int slice_ndim);
261+
extern bool array_iterate(ArrayIterator iterator, Datum *value, bool *isnull);
262+
extern void array_free_iterator(ArrayIterator iterator);
263+
257264
/*
258265
* prototypes for functions defined in arrayutils.c
259266
*/

0 commit comments

Comments
 (0)