]> granicus.if.org Git - postgresql/commitdiff
Add FOREACH IN ARRAY looping to plpgsql.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 16 Feb 2011 06:52:04 +0000 (01:52 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 16 Feb 2011 06:53:03 +0000 (01:53 -0500)
(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

doc/src/sgml/plpgsql.sgml
src/backend/utils/adt/arrayfuncs.c
src/include/utils/array.h
src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h
src/test/regress/expected/plpgsql.out
src/test/regress/sql/plpgsql.sql

index a2601e6bc898be99b15be4ae97e818fc350b04fa..c342916ff363ea952c497ae81575d1a944d89ad6 100644 (file)
@@ -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>
 
index 4ac9830878923ffeabf58247b2aca6266fb89fbc..e023b2458edd9d3e2964459f67a06f149e2a1a35 100644 (file)
@@ -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                       |*****************/
 /***************************************************************************/
index 7f7e744cb12bc872f628f90dad99dfdf074eb314..6bc280f14243091f5206fc7cfc495e654a780bba 100644 (file)
@@ -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
  */
index eae9bbad6c7efc69d7094d1598bc096c90b5f577..0ef6b5d48c3c0a1ae5e3da19d9f4a8f174e269a1 100644 (file)
@@ -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
index b685841d971edeca3f73d7e30fe798c58a81a030..7af6eee088e3d7868f7a0e05a3a747eabf539f72 100644 (file)
@@ -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;
@@ -2027,6 +2033,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
  *
index e24f71ac6c7bedbd6b2eb2462cd177f9895dd911..f13e4c3db6367c47ff75445fcad4eb7937c835a3 100644 (file)
@@ -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;
@@ -595,6 +601,23 @@ dump_forc(PLpgSQL_stmt_forc *stmt)
        printf("    ENDFORC\n");
 }
 
+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)
 {
index 6675184d613f5f93cd63f95516587e22688d885e..e8a2628f2f1dfbdd6ba466d7216c3a2ee0906d19 100644 (file)
@@ -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)
index 0ad7e28136bfb802c0715426018946d410c6085e..7015379842c364d9bd99dae3d26ec7466d214c2c 100644 (file)
@@ -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,
@@ -494,6 +495,18 @@ typedef struct
 } PLpgSQL_stmt_dynfors;
 
 
+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;
index 22ccce212c480f3a6406625825dc520c310f1e84..bfabcbc8b448379cffec1a0f3fb8568d8a0469a8 100644 (file)
@@ -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;
index d0f4e3b5e1f5c0f10f8f9cd48569cfd508b6b987..14fb4578c609b13cca464659bc456e34239557a3 100644 (file)
@@ -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;