Add FOREACH IN ARRAY looping to plpgsql.
authorTom Lane
Wed, 16 Feb 2011 06:52:04 +0000 (01:52 -0500)
committerTom Lane
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 '
 
 EXECUTE format('UPDATE tbl SET %I = %L WHERE key = %L', colname, newvalue, keyvalue);
 
-     The format function can be used in conjunction with 
+     The format function can be used in conjunction with
      the USING clause:
 
 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.
            
           
-
           
            
-            A FOR statement sets FOUND true
-            if it iterates one or more times, else false.  This applies to
-            all four variants of the FOR statement (integer
-            FOR loops, record-set FOR loops,
-            dynamic record-set FOR loops, and cursor
-            FOR loops).
+            A FOR or FOREACH statement sets
+            FOUND true
+            if it iterates one or more times, else false.
             FOUND is set this way when the
-            FOR loop exits; inside the execution of the loop,
+            loop exits; inside the execution of the loop,
             FOUND is not modified by the
-            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.
            
           
@@ -1910,9 +1906,9 @@ END CASE;
 
     
      With the LOOP, EXIT,
-     CONTINUE, WHILE, and FOR
-     statements, you can arrange for your PL/pgSQL
-     function to repeat a series of commands.
+     CONTINUE, WHILE, FOR,
+     and FOREACH statements, you can arrange for your
+     PL/pgSQL function to repeat a series of commands.
     
 
     
@@ -2238,6 +2234,90 @@ END LOOP  label ;
     
    
 
+   
+    Looping Through Arrays
+
+    
+     The FOREACH loop is much like a 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, 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 FOREACH statement to loop over an array is:
+
+
+ <<label>> 
+FOREACH target  SLICE number  IN ARRAY expression LOOP
+    statements
+END LOOP  label ;
+
+    
+
+    
+     Without SLICE, or if SLICE 0 is specified,
+     the loop iterates through individual elements of the array produced
+     by evaluating the expression.
+     The target 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:
+
+
+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;
+
+
+     The elements are visited in storage order, regardless of the number of
+     array dimensions.  Although the target 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.
+    
+
+    
+     With a positive SLICE value, FOREACH
+     iterates through slices of the array rather than single elements.
+     The SLICE value must be an integer constant not larger
+     than the number of dimensions of the array.  The
+     target 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 SLICE.
+     Here is an example of iterating through one-dimensional slices:
+
+
+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}
+
+    
+   
+
    
     Trapping Errors
 
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_until_then expr_until_loop opt_expr_until_when
 %type    opt_exitcond
 
-%type    assign_var
+%type    assign_var foreach_slice
 %type         cursor_variable
 %type   decl_cursor_arg
 %type     for_variable
@@ -190,7 +190,7 @@ static  List            *read_raise_options(void);
 %type    stmt_return stmt_raise stmt_execsql
 %type    stmt_dynexecute stmt_for stmt_perform stmt_getdiag
 %type    stmt_open stmt_fetch stmt_move stmt_close stmt_null
-%type    stmt_case
+%type    stmt_case stmt_foreach_a
 
 %type    proc_exceptions
 %type  exception_sect
@@ -239,6 +239,7 @@ static  List            *read_raise_options(void);
 %token    K_ABSOLUTE
 %token    K_ALIAS
 %token    K_ALL
+%token    K_ARRAY
 %token    K_BACKWARD
 %token    K_BEGIN
 %token    K_BY
@@ -264,6 +265,7 @@ static  List            *read_raise_options(void);
 %token    K_FETCH
 %token    K_FIRST
 %token    K_FOR
+%token    K_FOREACH
 %token    K_FORWARD
 %token    K_FROM
 %token    K_GET
@@ -298,6 +300,7 @@ static  List            *read_raise_options(void);
 %token    K_ROWTYPE
 %token    K_ROW_COUNT
 %token    K_SCROLL
+%token    K_SLICE
 %token    K_SQLSTATE
 %token    K_STRICT
 %token    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;