aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/plpgsql.sgml106
-rw-r--r--src/backend/utils/adt/arrayfuncs.c206
-rw-r--r--src/include/utils/array.h7
-rw-r--r--src/pl/plpgsql/src/gram.y63
-rw-r--r--src/pl/plpgsql/src/pl_exec.c185
-rw-r--r--src/pl/plpgsql/src/pl_funcs.c23
-rw-r--r--src/pl/plpgsql/src/pl_scanner.c3
-rw-r--r--src/pl/plpgsql/src/plpgsql.h13
-rw-r--r--src/test/regress/expected/plpgsql.out194
-rw-r--r--src/test/regress/sql/plpgsql.sql114
10 files changed, 899 insertions, 15 deletions
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index a2601e6bc89..c342916ff36 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1263,7 +1263,7 @@ EXECUTE 'UPDATE tbl SET '
<programlisting>
EXECUTE format('UPDATE tbl SET %I = %L WHERE key = %L', colname, newvalue, keyvalue);
</programlisting>
- The <function>format</function> function can be used in conjunction with
+ The <function>format</function> function can be used in conjunction with
the <literal>USING</literal> clause:
<programlisting>
EXECUTE format('UPDATE tbl SET %I = $1 WHERE key = $2', colname)
@@ -1356,19 +1356,15 @@ GET DIAGNOSTICS integer_var = ROW_COUNT;
true if it successfully repositions the cursor, false otherwise.
</para>
</listitem>
-
<listitem>
<para>
- A <command>FOR</> statement sets <literal>FOUND</literal> true
- if it iterates one or more times, else false. This applies to
- all four variants of the <command>FOR</> statement (integer
- <command>FOR</> loops, record-set <command>FOR</> loops,
- dynamic record-set <command>FOR</> loops, and cursor
- <command>FOR</> loops).
+ A <command>FOR</> or <command>FOREACH</> statement sets
+ <literal>FOUND</literal> true
+ if it iterates one or more times, else false.
<literal>FOUND</literal> is set this way when the
- <command>FOR</> loop exits; inside the execution of the loop,
+ loop exits; inside the execution of the loop,
<literal>FOUND</literal> is not modified by the
- <command>FOR</> statement, although it might be changed by the
+ loop statement, although it might be changed by the
execution of other statements within the loop body.
</para>
</listitem>
@@ -1910,9 +1906,9 @@ END CASE;
<para>
With the <literal>LOOP</>, <literal>EXIT</>,
- <literal>CONTINUE</>, <literal>WHILE</>, and <literal>FOR</>
- statements, you can arrange for your <application>PL/pgSQL</>
- function to repeat a series of commands.
+ <literal>CONTINUE</>, <literal>WHILE</>, <literal>FOR</>,
+ and <literal>FOREACH</> statements, you can arrange for your
+ <application>PL/pgSQL</> function to repeat a series of commands.
</para>
<sect3>
@@ -2238,6 +2234,90 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
</para>
</sect2>
+ <sect2 id="plpgsql-foreach-array">
+ <title>Looping Through Arrays</title>
+
+ <para>
+ The <literal>FOREACH</> loop is much like a <literal>FOR</> loop,
+ but instead of iterating through the rows returned by a SQL query,
+ it iterates through the elements of an array value.
+ (In general, <literal>FOREACH</> is meant for looping through
+ components of a composite-valued expression; variants for looping
+ through composites besides arrays may be added in future.)
+ The <literal>FOREACH</> statement to loop over an array is:
+
+<synopsis>
+<optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
+FOREACH <replaceable>target</replaceable> <optional> SLICE <replaceable>number</replaceable> </optional> IN ARRAY <replaceable>expression</replaceable> LOOP
+ <replaceable>statements</replaceable>
+END LOOP <optional> <replaceable>label</replaceable> </optional>;
+</synopsis>
+ </para>
+
+ <para>
+ Without <literal>SLICE</>, or if <literal>SLICE 0</> is specified,
+ the loop iterates through individual elements of the array produced
+ by evaluating the <replaceable>expression</replaceable>.
+ The <replaceable>target</replaceable> variable is assigned each
+ element value in sequence, and the loop body is executed for each element.
+ Here is an example of looping through the elements of an integer
+ array:
+
+<programlisting>
+CREATE FUNCTION sum(int[]) RETURNS int8 AS $$
+DECLARE
+ s int8 := 0;
+ x int;
+BEGIN
+ FOREACH x IN ARRAY $1
+ LOOP
+ s := s + x;
+ END LOOP;
+ RETURN s;
+END;
+$$ LANGUAGE plpgsql;
+</programlisting>
+
+ The elements are visited in storage order, regardless of the number of
+ array dimensions. Although the <replaceable>target</replaceable> is
+ usually just a single variable, it can be a list of variables when
+ looping through an array of composite values (records). In that case,
+ for each array element, the variables are assigned from successive
+ columns of the composite value.
+ </para>
+
+ <para>
+ With a positive <literal>SLICE</> value, <literal>FOREACH</>
+ iterates through slices of the array rather than single elements.
+ The <literal>SLICE</> value must be an integer constant not larger
+ than the number of dimensions of the array. The
+ <replaceable>target</replaceable> variable must be an array,
+ and it receives successive slices of the array value, where each slice
+ is of the number of dimensions specified by <literal>SLICE</>.
+ Here is an example of iterating through one-dimensional slices:
+
+<programlisting>
+CREATE FUNCTION scan_rows(int[]) RETURNS void AS $$
+DECLARE
+ x int[];
+BEGIN
+ FOREACH x SLICE 1 IN ARRAY $1
+ LOOP
+ RAISE NOTICE 'row = %', x;
+ END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT scan_rows(ARRAY[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]);
+
+NOTICE: row = {1,2,3}
+NOTICE: row = {4,5,6}
+NOTICE: row = {7,8,9}
+NOTICE: row = {10,11,12}
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="plpgsql-error-trapping">
<title>Trapping Errors</title>
diff --git a/src/backend/utils/adt/arrayfuncs.c b/src/backend/utils/adt/arrayfuncs.c
index 4ac98308789..e023b2458ed 100644
--- a/src/backend/utils/adt/arrayfuncs.c
+++ b/src/backend/utils/adt/arrayfuncs.c
@@ -50,6 +50,30 @@ typedef enum
ARRAY_LEVEL_DELIMITED
} ArrayParseState;
+/* Working state for array_iterate() */
+typedef struct ArrayIteratorData
+{
+ /* basic info about the array, set up during array_create_iterator() */
+ ArrayType *arr; /* array we're iterating through */
+ bits8 *nullbitmap; /* its null bitmap, if any */
+ int nitems; /* total number of elements in array */
+ int16 typlen; /* element type's length */
+ bool typbyval; /* element type's byval property */
+ char typalign; /* element type's align property */
+
+ /* information about the requested slice size */
+ int slice_ndim; /* slice dimension, or 0 if not slicing */
+ int slice_len; /* number of elements per slice */
+ int *slice_dims; /* slice dims array */
+ int *slice_lbound; /* slice lbound array */
+ Datum *slice_values; /* workspace of length slice_len */
+ bool *slice_nulls; /* workspace of length slice_len */
+
+ /* current position information, updated on each iteration */
+ char *data_ptr; /* our current position in the array */
+ int current_item; /* the item # we're at in the array */
+} ArrayIteratorData;
+
static bool array_isspace(char ch);
static int ArrayCount(const char *str, int *dim, char typdelim);
static void ReadArrayStr(char *arrayStr, const char *origStr,
@@ -3833,6 +3857,188 @@ arraycontained(PG_FUNCTION_ARGS)
}
+/*-----------------------------------------------------------------------------
+ * Array iteration functions
+ * These functions are used to iterate efficiently through arrays
+ *-----------------------------------------------------------------------------
+ */
+
+/*
+ * array_create_iterator --- set up to iterate through an array
+ *
+ * If slice_ndim is zero, we will iterate element-by-element; the returned
+ * datums are of the array's element type.
+ *
+ * If slice_ndim is 1..ARR_NDIM(arr), we will iterate by slices: the
+ * returned datums are of the same array type as 'arr', but of size
+ * equal to the rightmost N dimensions of 'arr'.
+ *
+ * The passed-in array must remain valid for the lifetime of the iterator.
+ */
+ArrayIterator
+array_create_iterator(ArrayType *arr, int slice_ndim)
+{
+ ArrayIterator iterator = palloc0(sizeof(ArrayIteratorData));
+
+ /*
+ * Sanity-check inputs --- caller should have got this right already
+ */
+ Assert(PointerIsValid(arr));
+ if (slice_ndim < 0 || slice_ndim > ARR_NDIM(arr))
+ elog(ERROR, "invalid arguments to array_create_iterator");
+
+ /*
+ * Remember basic info about the array and its element type
+ */
+ iterator->arr = arr;
+ iterator->nullbitmap = ARR_NULLBITMAP(arr);
+ iterator->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ &iterator->typlen,
+ &iterator->typbyval,
+ &iterator->typalign);
+
+ /*
+ * Remember the slicing parameters.
+ */
+ iterator->slice_ndim = slice_ndim;
+
+ if (slice_ndim > 0)
+ {
+ /*
+ * Get pointers into the array's dims and lbound arrays to represent
+ * the dims/lbound arrays of a slice. These are the same as the
+ * rightmost N dimensions of the array.
+ */
+ iterator->slice_dims = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim;
+ iterator->slice_lbound = ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim;
+
+ /*
+ * Compute number of elements in a slice.
+ */
+ iterator->slice_len = ArrayGetNItems(slice_ndim,
+ iterator->slice_dims);
+
+ /*
+ * Create workspace for building sub-arrays.
+ */
+ iterator->slice_values = (Datum *)
+ palloc(iterator->slice_len * sizeof(Datum));
+ iterator->slice_nulls = (bool *)
+ palloc(iterator->slice_len * sizeof(bool));
+ }
+
+ /*
+ * Initialize our data pointer and linear element number. These will
+ * advance through the array during array_iterate().
+ */
+ iterator->data_ptr = ARR_DATA_PTR(arr);
+ iterator->current_item = 0;
+
+ return iterator;
+}
+
+/*
+ * Iterate through the array referenced by 'iterator'.
+ *
+ * As long as there is another element (or slice), return it into
+ * *value / *isnull, and return true. Return false when no more data.
+ */
+bool
+array_iterate(ArrayIterator iterator, Datum *value, bool *isnull)
+{
+ /* Done if we have reached the end of the array */
+ if (iterator->current_item >= iterator->nitems)
+ return false;
+
+ if (iterator->slice_ndim == 0)
+ {
+ /*
+ * Scalar case: return one element.
+ */
+ if (array_get_isnull(iterator->nullbitmap, iterator->current_item++))
+ {
+ *isnull = true;
+ *value = (Datum) 0;
+ }
+ else
+ {
+ /* non-NULL, so fetch the individual Datum to return */
+ char *p = iterator->data_ptr;
+
+ *isnull = false;
+ *value = fetch_att(p, iterator->typbyval, iterator->typlen);
+
+ /* Move our data pointer forward to the next element */
+ p = att_addlength_pointer(p, iterator->typlen, p);
+ p = (char *) att_align_nominal(p, iterator->typalign);
+ iterator->data_ptr = p;
+ }
+ }
+ else
+ {
+ /*
+ * Slice case: build and return an array of the requested size.
+ */
+ ArrayType *result;
+ Datum *values = iterator->slice_values;
+ bool *nulls = iterator->slice_nulls;
+ char *p = iterator->data_ptr;
+ int i;
+
+ for (i = 0; i < iterator->slice_len; i++)
+ {
+ if (array_get_isnull(iterator->nullbitmap,
+ iterator->current_item++))
+ {
+ nulls[i] = true;
+ values[i] = (Datum) 0;
+ }
+ else
+ {
+ nulls[i] = false;
+ values[i] = fetch_att(p, iterator->typbyval, iterator->typlen);
+
+ /* Move our data pointer forward to the next element */
+ p = att_addlength_pointer(p, iterator->typlen, p);
+ p = (char *) att_align_nominal(p, iterator->typalign);
+ }
+ }
+
+ iterator->data_ptr = p;
+
+ result = construct_md_array(values,
+ nulls,
+ iterator->slice_ndim,
+ iterator->slice_dims,
+ iterator->slice_lbound,
+ ARR_ELEMTYPE(iterator->arr),
+ iterator->typlen,
+ iterator->typbyval,
+ iterator->typalign);
+
+ *isnull = false;
+ *value = PointerGetDatum(result);
+ }
+
+ return true;
+}
+
+/*
+ * Release an ArrayIterator data structure
+ */
+void
+array_free_iterator(ArrayIterator iterator)
+{
+ if (iterator->slice_ndim > 0)
+ {
+ pfree(iterator->slice_values);
+ pfree(iterator->slice_nulls);
+ }
+ pfree(iterator);
+}
+
+
/***************************************************************************/
/******************| Support Routines |*****************/
/***************************************************************************/
diff --git a/src/include/utils/array.h b/src/include/utils/array.h
index 7f7e744cb12..6bc280f1424 100644
--- a/src/include/utils/array.h
+++ b/src/include/utils/array.h
@@ -114,6 +114,9 @@ typedef struct ArrayMapState
ArrayMetaState ret_extra;
} ArrayMapState;
+/* ArrayIteratorData is private in arrayfuncs.c */
+typedef struct ArrayIteratorData *ArrayIterator;
+
/*
* fmgr macros for array objects
*/
@@ -254,6 +257,10 @@ extern Datum makeArrayResult(ArrayBuildState *astate,
extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
int *dims, int *lbs, MemoryContext rcontext, bool release);
+extern ArrayIterator array_create_iterator(ArrayType *arr, int slice_ndim);
+extern bool array_iterate(ArrayIterator iterator, Datum *value, bool *isnull);
+extern void array_free_iterator(ArrayIterator iterator);
+
/*
* prototypes for functions defined in arrayutils.c
*/
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index eae9bbad6c7..0ef6b5d48c3 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -175,7 +175,7 @@ static List *read_raise_options(void);
%type <expr> expr_until_then expr_until_loop opt_expr_until_when
%type <expr> opt_exitcond
-%type <ival> assign_var
+%type <ival> assign_var foreach_slice
%type <var> cursor_variable
%type <datum> decl_cursor_arg
%type <forvariable> for_variable
@@ -190,7 +190,7 @@ static List *read_raise_options(void);
%type <stmt> stmt_return stmt_raise stmt_execsql
%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_getdiag
%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
-%type <stmt> stmt_case
+%type <stmt> stmt_case stmt_foreach_a
%type <list> proc_exceptions
%type <exception_block> exception_sect
@@ -239,6 +239,7 @@ static List *read_raise_options(void);
%token <keyword> K_ABSOLUTE
%token <keyword> K_ALIAS
%token <keyword> K_ALL
+%token <keyword> K_ARRAY
%token <keyword> K_BACKWARD
%token <keyword> K_BEGIN
%token <keyword> K_BY
@@ -264,6 +265,7 @@ static List *read_raise_options(void);
%token <keyword> K_FETCH
%token <keyword> K_FIRST
%token <keyword> K_FOR
+%token <keyword> K_FOREACH
%token <keyword> K_FORWARD
%token <keyword> K_FROM
%token <keyword> K_GET
@@ -298,6 +300,7 @@ static List *read_raise_options(void);
%token <keyword> K_ROWTYPE
%token <keyword> K_ROW_COUNT
%token <keyword> K_SCROLL
+%token <keyword> K_SLICE
%token <keyword> K_SQLSTATE
%token <keyword> K_STRICT
%token <keyword> K_THEN
@@ -739,6 +742,8 @@ proc_stmt : pl_block ';'
{ $$ = $1; }
| stmt_for
{ $$ = $1; }
+ | stmt_foreach_a
+ { $$ = $1; }
| stmt_exit
{ $$ = $1; }
| stmt_return
@@ -1386,6 +1391,58 @@ for_variable : T_DATUM
}
;
+stmt_foreach_a : opt_block_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body
+ {
+ PLpgSQL_stmt_foreach_a *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_foreach_a));
+ new->cmd_type = PLPGSQL_STMT_FOREACH_A;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->label = $1;
+ new->slice = $4;
+ new->expr = $7;
+ new->body = $8.stmts;
+
+ if ($3.rec)
+ {
+ new->varno = $3.rec->dno;
+ check_assignable((PLpgSQL_datum *) $3.rec, @3);
+ }
+ else if ($3.row)
+ {
+ new->varno = $3.row->dno;
+ check_assignable((PLpgSQL_datum *) $3.row, @3);
+ }
+ else if ($3.scalar)
+ {
+ new->varno = $3.scalar->dno;
+ check_assignable($3.scalar, @3);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("loop variable of FOREACH must be a known variable or list of variables"),
+ parser_errposition(@3)));
+ }
+
+ check_labels($1, $8.end_label, $8.end_label_location);
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+foreach_slice :
+ {
+ $$ = 0;
+ }
+ | K_SLICE ICONST
+ {
+ $$ = $2;
+ }
+ ;
+
stmt_exit : exit_type opt_label opt_exitcond
{
PLpgSQL_stmt_exit *new;
@@ -2035,6 +2092,7 @@ any_identifier : T_WORD
unreserved_keyword :
K_ABSOLUTE
| K_ALIAS
+ | K_ARRAY
| K_BACKWARD
| K_CONSTANT
| K_CURSOR
@@ -2063,6 +2121,7 @@ unreserved_keyword :
| K_ROW_COUNT
| K_ROWTYPE
| K_SCROLL
+ | K_SLICE
| K_SQLSTATE
| K_TYPE
| K_USE_COLUMN
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b685841d971..7af6eee088e 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -107,6 +107,8 @@ static int exec_stmt_fors(PLpgSQL_execstate *estate,
PLpgSQL_stmt_fors *stmt);
static int exec_stmt_forc(PLpgSQL_execstate *estate,
PLpgSQL_stmt_forc *stmt);
+static int exec_stmt_foreach_a(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_foreach_a *stmt);
static int exec_stmt_open(PLpgSQL_execstate *estate,
PLpgSQL_stmt_open *stmt);
static int exec_stmt_fetch(PLpgSQL_execstate *estate,
@@ -1312,6 +1314,10 @@ exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
break;
+ case PLPGSQL_STMT_FOREACH_A:
+ rc = exec_stmt_foreach_a(estate, (PLpgSQL_stmt_foreach_a *) stmt);
+ break;
+
case PLPGSQL_STMT_EXIT:
rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
break;
@@ -2028,6 +2034,185 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
/* ----------
+ * exec_stmt_foreach_a Loop over elements or slices of an array
+ *
+ * When looping over elements, the loop variable is the same type that the
+ * array stores (eg: integer), when looping through slices, the loop variable
+ * is an array of size and dimensions to match the size of the slice.
+ * ----------
+ */
+static int
+exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
+{
+ ArrayType *arr;
+ Oid arrtype;
+ PLpgSQL_datum *loop_var;
+ Oid loop_var_elem_type;
+ bool found = false;
+ int rc = PLPGSQL_RC_OK;
+ ArrayIterator array_iterator;
+ Oid iterator_result_type;
+ Datum value;
+ bool isnull;
+
+ /* get the value of the array expression */
+ value = exec_eval_expr(estate, stmt->expr, &isnull, &arrtype);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("FOREACH expression must not be NULL")));
+
+ /* check the type of the expression - must be an array */
+ if (!OidIsValid(get_element_type(arrtype)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH expression must yield an array, not type %s",
+ format_type_be(arrtype))));
+
+ /*
+ * We must copy the array, else it will disappear in exec_eval_cleanup.
+ * This is annoying, but cleanup will certainly happen while running the
+ * loop body, so we have little choice.
+ */
+ arr = DatumGetArrayTypePCopy(value);
+
+ /* Clean up any leftover temporary memory */
+ exec_eval_cleanup(estate);
+
+ /* Slice dimension must be less than or equal to array dimension */
+ if (stmt->slice < 0 || stmt->slice > ARR_NDIM(arr))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("slice dimension (%d) is out of the valid range 0..%d",
+ stmt->slice, ARR_NDIM(arr))));
+
+ /* Set up the loop variable and see if it is of an array type */
+ loop_var = estate->datums[stmt->varno];
+ if (loop_var->dtype == PLPGSQL_DTYPE_REC ||
+ loop_var->dtype == PLPGSQL_DTYPE_ROW)
+ {
+ /*
+ * Record/row variable is certainly not of array type, and might not
+ * be initialized at all yet, so don't try to get its type
+ */
+ loop_var_elem_type = InvalidOid;
+ }
+ else
+ loop_var_elem_type = get_element_type(exec_get_datum_type(estate,
+ loop_var));
+
+ /*
+ * Sanity-check the loop variable type. We don't try very hard here,
+ * and should not be too picky since it's possible that exec_assign_value
+ * can coerce values of different types. But it seems worthwhile to
+ * complain if the array-ness of the loop variable is not right.
+ */
+ if (stmt->slice > 0 && loop_var_elem_type == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH ... SLICE loop variable must be of an array type")));
+ if (stmt->slice == 0 && loop_var_elem_type != InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH loop variable must not be of an array type")));
+
+ /* Create an iterator to step through the array */
+ array_iterator = array_create_iterator(arr, stmt->slice);
+
+ /* Identify iterator result type */
+ if (stmt->slice > 0)
+ {
+ /* When slicing, nominal type of result is same as array type */
+ iterator_result_type = arrtype;
+ }
+ else
+ {
+ /* Without slicing, results are individual array elements */
+ iterator_result_type = ARR_ELEMTYPE(arr);
+ }
+
+ /* Iterate over the array elements or slices */
+ while (array_iterate(array_iterator, &value, &isnull))
+ {
+ found = true; /* looped at least once */
+
+ /* Assign current element/slice to the loop variable */
+ exec_assign_value(estate, loop_var, value, iterator_result_type,
+ &isnull);
+
+ /* In slice case, value is temporary; must free it to avoid leakage */
+ if (stmt->slice > 0)
+ pfree(DatumGetPointer(value));
+
+ /*
+ * Execute the statements
+ */
+ rc = exec_stmts(estate, stmt->body);
+
+ /* Handle the return code */
+ if (rc == PLPGSQL_RC_RETURN)
+ break; /* break out of the loop */
+ else if (rc == PLPGSQL_RC_EXIT)
+ {
+ if (estate->exitlabel == NULL)
+ /* unlabelled exit, finish the current loop */
+ rc = PLPGSQL_RC_OK;
+ else if (stmt->label != NULL &&
+ strcmp(stmt->label, estate->exitlabel) == 0)
+ {
+ /* labelled exit, matches the current stmt's label */
+ estate->exitlabel = NULL;
+ rc = PLPGSQL_RC_OK;
+ }
+
+ /*
+ * otherwise, this is a labelled exit that does not match the
+ * current statement's label, if any: return RC_EXIT so that the
+ * EXIT continues to propagate up the stack.
+ */
+ break;
+ }
+ else if (rc == PLPGSQL_RC_CONTINUE)
+ {
+ if (estate->exitlabel == NULL)
+ /* unlabelled continue, so re-run the current loop */
+ rc = PLPGSQL_RC_OK;
+ else if (stmt->label != NULL &&
+ strcmp(stmt->label, estate->exitlabel) == 0)
+ {
+ /* label matches named continue, so re-run loop */
+ estate->exitlabel = NULL;
+ rc = PLPGSQL_RC_OK;
+ }
+ else
+ {
+ /*
+ * otherwise, this is a named continue that does not match the
+ * current statement's label, if any: return RC_CONTINUE so
+ * that the CONTINUE will propagate up the stack.
+ */
+ break;
+ }
+ }
+ }
+
+ /* Release temporary memory, including the array value */
+ array_free_iterator(array_iterator);
+ pfree(arr);
+
+ /*
+ * Set the FOUND variable to indicate the result of executing the loop
+ * (namely, whether we looped one or more times). This must be set here so
+ * that it does not interfere with the value of the FOUND variable inside
+ * the loop processing itself.
+ */
+ exec_set_found(estate, found);
+
+ return rc;
+}
+
+
+/* ----------
* exec_stmt_exit Implements EXIT and CONTINUE
*
* This begins the process of exiting / restarting a loop.
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index e24f71ac6c7..f13e4c3db63 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -230,6 +230,8 @@ plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
return _("FOR over SELECT rows");
case PLPGSQL_STMT_FORC:
return _("FOR over cursor");
+ case PLPGSQL_STMT_FOREACH_A:
+ return _("FOREACH over array");
case PLPGSQL_STMT_EXIT:
return "EXIT";
case PLPGSQL_STMT_RETURN:
@@ -278,6 +280,7 @@ static void dump_while(PLpgSQL_stmt_while *stmt);
static void dump_fori(PLpgSQL_stmt_fori *stmt);
static void dump_fors(PLpgSQL_stmt_fors *stmt);
static void dump_forc(PLpgSQL_stmt_forc *stmt);
+static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
static void dump_exit(PLpgSQL_stmt_exit *stmt);
static void dump_return(PLpgSQL_stmt_return *stmt);
static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
@@ -337,6 +340,9 @@ dump_stmt(PLpgSQL_stmt *stmt)
case PLPGSQL_STMT_FORC:
dump_forc((PLpgSQL_stmt_forc *) stmt);
break;
+ case PLPGSQL_STMT_FOREACH_A:
+ dump_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ break;
case PLPGSQL_STMT_EXIT:
dump_exit((PLpgSQL_stmt_exit *) stmt);
break;
@@ -596,6 +602,23 @@ dump_forc(PLpgSQL_stmt_forc *stmt)
}
static void
+dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+{
+ dump_ind();
+ printf("FOREACHA var %d ", stmt->varno);
+ if (stmt->slice != 0)
+ printf("SLICE %d ", stmt->slice);
+ printf("IN ");
+ dump_expr(stmt->expr);
+ printf("\n");
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDFOREACHA");
+}
+
+static void
dump_open(PLpgSQL_stmt_open *stmt)
{
dump_ind();
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index 6675184d613..e8a2628f2f1 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -77,6 +77,7 @@ static const ScanKeyword reserved_keywords[] = {
PG_KEYWORD("exit", K_EXIT, RESERVED_KEYWORD)
PG_KEYWORD("fetch", K_FETCH, RESERVED_KEYWORD)
PG_KEYWORD("for", K_FOR, RESERVED_KEYWORD)
+ PG_KEYWORD("foreach", K_FOREACH, RESERVED_KEYWORD)
PG_KEYWORD("from", K_FROM, RESERVED_KEYWORD)
PG_KEYWORD("get", K_GET, RESERVED_KEYWORD)
PG_KEYWORD("if", K_IF, RESERVED_KEYWORD)
@@ -105,6 +106,7 @@ static const int num_reserved_keywords = lengthof(reserved_keywords);
static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
+ PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
@@ -133,6 +135,7 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
+ PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 0ad7e28136b..7015379842c 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -90,6 +90,7 @@ enum PLpgSQL_stmt_types
PLPGSQL_STMT_FORI,
PLPGSQL_STMT_FORS,
PLPGSQL_STMT_FORC,
+ PLPGSQL_STMT_FOREACH_A,
PLPGSQL_STMT_EXIT,
PLPGSQL_STMT_RETURN,
PLPGSQL_STMT_RETURN_NEXT,
@@ -495,6 +496,18 @@ typedef struct
typedef struct
+{ /* FOREACH item in array loop */
+ int cmd_type;
+ int lineno;
+ char *label;
+ int varno; /* loop target variable */
+ int slice; /* slice dimension, or 0 */
+ PLpgSQL_expr *expr; /* array expression */
+ List *body; /* List of statements */
+} PLpgSQL_stmt_foreach_a;
+
+
+typedef struct
{ /* OPEN a curvar */
int cmd_type;
int lineno;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 22ccce212c4..bfabcbc8b44 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4240,3 +4240,197 @@ select unreserved_test();
(1 row)
drop function unreserved_test();
+--
+-- Test FOREACH over arrays
+--
+create function foreach_test(anyarray)
+returns void as $$
+declare x int;
+begin
+ foreach x in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+select foreach_test(ARRAY[1,2,3,4]);
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+NOTICE: 4
+ foreach_test
+--------------
+
+(1 row)
+
+select foreach_test(ARRAY[[1,2],[3,4]]);
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+NOTICE: 4
+ foreach_test
+--------------
+
+(1 row)
+
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int;
+begin
+ foreach x slice 1 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+-- should fail
+select foreach_test(ARRAY[1,2,3,4]);
+ERROR: FOREACH ... SLICE loop variable must be of an array type
+CONTEXT: PL/pgSQL function "foreach_test" line 4 at FOREACH over array
+select foreach_test(ARRAY[[1,2],[3,4]]);
+ERROR: FOREACH ... SLICE loop variable must be of an array type
+CONTEXT: PL/pgSQL function "foreach_test" line 4 at FOREACH over array
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int[];
+begin
+ foreach x slice 1 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+select foreach_test(ARRAY[1,2,3,4]);
+NOTICE: {1,2,3,4}
+ foreach_test
+--------------
+
+(1 row)
+
+select foreach_test(ARRAY[[1,2],[3,4]]);
+NOTICE: {1,2}
+NOTICE: {3,4}
+ foreach_test
+--------------
+
+(1 row)
+
+-- higher level of slicing
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int[];
+begin
+ foreach x slice 2 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+-- should fail
+select foreach_test(ARRAY[1,2,3,4]);
+ERROR: slice dimension (2) is out of the valid range 0..1
+CONTEXT: PL/pgSQL function "foreach_test" line 4 at FOREACH over array
+-- ok
+select foreach_test(ARRAY[[1,2],[3,4]]);
+NOTICE: {{1,2},{3,4}}
+ foreach_test
+--------------
+
+(1 row)
+
+select foreach_test(ARRAY[[[1,2]],[[3,4]]]);
+NOTICE: {{1,2}}
+NOTICE: {{3,4}}
+ foreach_test
+--------------
+
+(1 row)
+
+create type xy_tuple AS (x int, y int);
+-- iteration over array of records
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare r record;
+begin
+ foreach r in array $1
+ loop
+ raise notice '%', r;
+ end loop;
+ end;
+$$ language plpgsql;
+select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
+NOTICE: (10,20)
+NOTICE: (40,69)
+NOTICE: (35,78)
+ foreach_test
+--------------
+
+(1 row)
+
+select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
+NOTICE: (10,20)
+NOTICE: (40,69)
+NOTICE: (35,78)
+NOTICE: (88,76)
+ foreach_test
+--------------
+
+(1 row)
+
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int; y int;
+begin
+ foreach x, y in array $1
+ loop
+ raise notice 'x = %, y = %', x, y;
+ end loop;
+ end;
+$$ language plpgsql;
+select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
+NOTICE: x = 10, y = 20
+NOTICE: x = 40, y = 69
+NOTICE: x = 35, y = 78
+ foreach_test
+--------------
+
+(1 row)
+
+select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
+NOTICE: x = 10, y = 20
+NOTICE: x = 40, y = 69
+NOTICE: x = 35, y = 78
+NOTICE: x = 88, y = 76
+ foreach_test
+--------------
+
+(1 row)
+
+-- slicing over array of composite types
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x xy_tuple[];
+begin
+ foreach x slice 1 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
+NOTICE: {"(10,20)","(40,69)","(35,78)"}
+ foreach_test
+--------------
+
+(1 row)
+
+select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
+NOTICE: {"(10,20)","(40,69)"}
+NOTICE: {"(35,78)","(88,76)"}
+ foreach_test
+--------------
+
+(1 row)
+
+drop function foreach_test(anyarray);
+drop type xy_tuple;
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index d0f4e3b5e1f..14fb4578c60 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3375,3 +3375,117 @@ $$ language plpgsql;
select unreserved_test();
drop function unreserved_test();
+
+--
+-- Test FOREACH over arrays
+--
+
+create function foreach_test(anyarray)
+returns void as $$
+declare x int;
+begin
+ foreach x in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+
+select foreach_test(ARRAY[1,2,3,4]);
+select foreach_test(ARRAY[[1,2],[3,4]]);
+
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int;
+begin
+ foreach x slice 1 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+
+-- should fail
+select foreach_test(ARRAY[1,2,3,4]);
+select foreach_test(ARRAY[[1,2],[3,4]]);
+
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int[];
+begin
+ foreach x slice 1 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+
+select foreach_test(ARRAY[1,2,3,4]);
+select foreach_test(ARRAY[[1,2],[3,4]]);
+
+-- higher level of slicing
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int[];
+begin
+ foreach x slice 2 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+
+-- should fail
+select foreach_test(ARRAY[1,2,3,4]);
+-- ok
+select foreach_test(ARRAY[[1,2],[3,4]]);
+select foreach_test(ARRAY[[[1,2]],[[3,4]]]);
+
+create type xy_tuple AS (x int, y int);
+
+-- iteration over array of records
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare r record;
+begin
+ foreach r in array $1
+ loop
+ raise notice '%', r;
+ end loop;
+ end;
+$$ language plpgsql;
+
+select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
+select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
+
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x int; y int;
+begin
+ foreach x, y in array $1
+ loop
+ raise notice 'x = %, y = %', x, y;
+ end loop;
+ end;
+$$ language plpgsql;
+
+select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
+select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
+
+-- slicing over array of composite types
+create or replace function foreach_test(anyarray)
+returns void as $$
+declare x xy_tuple[];
+begin
+ foreach x slice 1 in array $1
+ loop
+ raise notice '%', x;
+ end loop;
+ end;
+$$ language plpgsql;
+
+select foreach_test(ARRAY[(10,20),(40,69),(35,78)]::xy_tuple[]);
+select foreach_test(ARRAY[[(10,20),(40,69)],[(35,78),(88,76)]]::xy_tuple[]);
+
+drop function foreach_test(anyarray);
+drop type xy_tuple;