updated patch for foreach stmt

Started by Pavel Stehulealmost 15 years ago15 messages
#1Pavel Stehule
pavel.stehule@gmail.com
1 attachment(s)

Hello

I resend a patch with last update of this patch

Regards

Pavel Stehule

Attachments:

foreach_array_reader.difftext/x-patch; charset=US-ASCII; name=foreach_array_reader.diffDownload
*** ./doc/src/sgml/plpgsql.sgml.orig	2011-01-30 10:04:52.097002835 +0100
--- ./doc/src/sgml/plpgsql.sgml	2011-01-30 10:05:21.379877524 +0100
***************
*** 2238,2243 ****
--- 2238,2268 ----
      </para>
     </sect2>
  
+    <sect2 id="plpgsql-array-iterating">
+     <title>Looping Through Array</title>
+ <synopsis>
+ <optional> &lt;&lt;<replaceable>label</replaceable>&gt;&gt; </optional>
+ FOREACH <replaceable>target</replaceable> <optional> SCALE <replaceable>number</replaceable> </optional> IN <replaceable>expression</replaceable> LOOP
+     <replaceable>statements</replaceable>
+ END LOOP <optional> <replaceable>label</replaceable> </optional>;
+ </synopsis>
+ 
+ <programlisting>
+ CREATE FUNCTION sum(VARIADIC int[]) RETURNS int8 AS $$
+ DECLARE
+   s int8; x int;
+ BEGIN
+   FOREACH x IN $1
+   LOOP
+     s := s + x;
+   END LOOP;
+   RETURN s;
+ END;
+ $$ LANGUAGE plpgsql;
+ </programlisting>
+ 
+    </sect2>
+ 
     <sect2 id="plpgsql-error-trapping">
      <title>Trapping Errors</title>
  
*** ./src/backend/utils/adt/arrayfuncs.c.orig	2011-01-29 12:52:53.109667995 +0100
--- ./src/backend/utils/adt/arrayfuncs.c	2011-01-30 10:05:21.390878981 +0100
***************
*** 68,74 ****
  			 Datum *values, bool *nulls, int nitems,
  			 int typlen, bool typbyval, char typalign,
  			 bool freedata);
- static bool array_get_isnull(const bits8 *nullbitmap, int offset);
  static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull);
  static Datum ArrayCast(char *value, bool byval, int len);
  static int ArrayCastAndSet(Datum src,
--- 68,73 ----
***************
*** 3840,3846 ****
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! static bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
--- 3839,3845 ----
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
*** ./src/include/utils/array.h.orig	2011-01-30 10:04:52.142008793 +0100
--- ./src/include/utils/array.h	2011-01-30 10:05:21.404880832 +0100
***************
*** 215,220 ****
--- 215,221 ----
  extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
  		  Datum dataValue, bool isNull,
  		  int arraytyplen, int elmlen, bool elmbyval, char elmalign);
+ extern bool array_get_isnull(const bits8 *nullbitmap, int offset);
  extern ArrayType *array_get_slice(ArrayType *array, int nSubscripts,
  				int *upperIndx, int *lowerIndx,
  				int arraytyplen, int elmlen, bool elmbyval, char elmalign);
*** ./src/pl/plpgsql/src/gram.y.orig	2011-01-30 10:04:52.158010906 +0100
--- ./src/pl/plpgsql/src/gram.y	2011-01-30 10:05:21.408881362 +0100
***************
*** 21,26 ****
--- 21,27 ----
  #include "parser/parse_type.h"
  #include "parser/scanner.h"
  #include "parser/scansup.h"
+ #include "utils/array.h"
  
  
  /* Location tracking support --- simpler than bison's default */
***************
*** 134,139 ****
--- 135,141 ----
  			PLpgSQL_datum   *scalar;
  			PLpgSQL_rec     *rec;
  			PLpgSQL_row     *row;
+ 			int	slice;
  		}						forvariable;
  		struct
  		{
***************
*** 178,184 ****
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
--- 180,186 ----
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable foreach_control
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
***************
*** 190,196 ****
  %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 <list>	proc_exceptions
  %type <exception_block> exception_sect
--- 192,198 ----
  %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 stmt_foreach_a
  
  %type <list>	proc_exceptions
  %type <exception_block> exception_sect
***************
*** 264,269 ****
--- 266,272 ----
  %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,303 ****
--- 301,307 ----
  %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,744 ****
--- 743,750 ----
  						{ $$ = $1; }
  				| stmt_for
  						{ $$ = $1; }
+ 				| stmt_foreach_a
+ 						{ $$ = $1; }
  				| stmt_exit
  						{ $$ = $1; }
  				| stmt_return
***************
*** 1386,1391 ****
--- 1392,1455 ----
  					}
  				;
  
+ stmt_foreach_a		: opt_block_label K_FOREACH foreach_control K_IN expr_until_loop loop_body
+ 					{
+ 						PLpgSQL_stmt_foreach_a *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->expr = $5;
+ 						new->slice = $3.slice;
+ 						new->body = $6.stmts;
+ 
+ 						if ($3.rec)
+ 						{
+ 							new->rec = $3.rec;
+ 							check_assignable((PLpgSQL_datum *) new->rec, @3);
+ 						}
+ 						else if ($3.row)
+ 						{
+ 							new->row = $3.row;
+ 							check_assignable((PLpgSQL_datum *) new->row, @3);
+ 						}
+ 						else if ($3.scalar)
+ 						{
+ 							Assert($3.scalar->dtype == PLPGSQL_DTYPE_VAR);
+ 							new->var = (PLpgSQL_var *) $3.scalar;
+ 							check_assignable($3.scalar, @3);
+ 
+ 						}
+ 						else
+ 						{
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_SYNTAX_ERROR),
+ 									 errmsg("loop variable of loop over array must be a record, row variable, scalar variable or list of scalar variables"),
+ 											 parser_errposition(@3)));
+ 						}
+ 
+ 						check_labels($1, $6.end_label, $6.end_label_location);
+ 						$$ = (PLpgSQL_stmt *) new;
+ 					}
+ 				;
+ 
+ foreach_control		: for_variable
+ 					{
+ 						$$ = $1;
+ 						$$.slice = 0;
+ 					}
+ 				| for_variable K_SLICE ICONST
+ 					{
+ 						$$ = $1;
+ 						$$.slice = $3;
+ 						if ($3 < 0 || $3 >= MAXDIM)
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 									 errmsg("number of slicing array dimensions (%d) exceeds the maximum allowed (%d)",
+ 												$3, MAXDIM),
+ 											 parser_errposition(@3)));
+ 					}
+ 				;
+ 
  stmt_exit		: exit_type opt_label opt_exitcond
  					{
  						PLpgSQL_stmt_exit *new;
***************
*** 2063,2068 ****
--- 2127,2133 ----
  				| K_ROW_COUNT
  				| K_ROWTYPE
  				| K_SCROLL
+ 				| K_SLICE
  				| K_SQLSTATE
  				| K_TYPE
  				| K_USE_COLUMN
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2011-01-30 10:04:52.174013026 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2011-01-30 12:15:48.510834317 +0100
***************
*** 48,53 ****
--- 48,70 ----
  	bool	   *freevals;		/* which arguments are pfree-able */
  } PreparedParamsData;
  
+ typedef struct
+ {
+ 	int			nitems;			/* number of elements in array */
+ 	ArrayType		*arr;			/* processed array */
+ 	char		*data_ptr;		/* pointer to data in array */
+ 	bits8		*nulls_ptr;		/* pointer to null's bitmap in array */
+ 	int		current_item;		/* number of processed element in array */
+ 	Oid	    elmtype;		    /* element's type */
+ 	int16	    elmlen;		    /* element's length */
+ 	bool	    elmbyval;		    /* element's byval property */
+ 	char	    elmalign;		    /* align type of element */
+ 	int	slice_ndim;		/* slice dimension */
+ 	int	slice_len;		/* number of elements per slice */
+ } ArrayReaderData;
+ 
+ typedef ArrayReaderData *ArrayReader;
+ 
  /*
   * All plpgsql function executions within a single transaction share the same
   * executor EState for evaluating "simple" expressions.  Each function call
***************
*** 107,112 ****
--- 124,131 ----
  			   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,1317 ****
--- 1331,1340 ----
  			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;
***************
*** 2026,2031 ****
--- 2049,2335 ----
  	return rc;
  }
  
+ /*
+  * Create a state value for repeatable reading from array
+  */
+ static ArrayReader
+ create_array_reader(ArrayType *arr, int slice_ndim)
+ {
+ 	ArrayReader reader = palloc(sizeof(ArrayReaderData));
+ 
+ 	reader->arr = arr;
+ 	reader->data_ptr = ARR_DATA_PTR(arr);
+ 	reader->nulls_ptr = ARR_NULLBITMAP(arr);
+ 	reader->elmtype = ARR_ELEMTYPE(arr);
+ 
+ 	get_typlenbyvalalign(reader->elmtype,
+ 						&reader->elmlen,
+ 						&reader->elmbyval,
+ 						&reader->elmalign);
+ 
+ 	/* Calculate the number of elements we're going to loop through */
+ 	reader->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ 	reader->current_item = 0;
+ 
+ 	reader->slice_ndim = slice_ndim;
+ 	reader->slice_len = slice_ndim > 0 ? ArrayGetNItems(slice_ndim, ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim) : 0;
+ 
+ 	return reader;
+ }
+ 
+ /*
+  * Seq reading of array's data
+  */
+ static bool
+ read_from_array(ArrayReader reader, Datum *value, bool *isnull)
+ {
+ 	if (reader->current_item < reader->nitems)
+ 	{
+ 		if (reader->slice_len > 0)
+ 		{
+ 			int	i;
+ 			ArrayBuildState *astate = NULL;
+ 
+ 			/* Returns a slice. Copy a "slice_len" elements to new array. */
+ 			for (i = 0; i < reader->slice_len; i++)
+ 			{
+ 				if (array_get_isnull(reader->nulls_ptr, reader->current_item++))
+ 				{
+ 					astate = accumArrayResult(astate, (Datum) 0, true,
+ 											reader->elmtype,
+ 											CurrentMemoryContext);
+ 				}
+ 				else
+ 				{
+ 					Datum	element;
+ 
+ 					element = fetch_att(reader->data_ptr, reader->elmbyval, reader->elmlen);
+ 					astate = accumArrayResult(astate, element, false,
+ 											reader->elmtype,
+ 											CurrentMemoryContext);
+ 
+ 					reader->data_ptr = att_addlength_pointer(reader->data_ptr, reader->elmlen, reader->data_ptr);
+ 					reader->data_ptr = (char *) att_align_nominal(reader->data_ptr, reader->elmalign);
+ 				}
+ 			}
+ 
+ 			/* Build a final array */
+ 			*isnull = false;
+ 			*value = makeMdArrayResult(astate, reader->slice_ndim,
+ 									    ARR_DIMS(reader->arr) + ARR_NDIM(reader->arr) - reader->slice_ndim,
+ 									    ARR_LBOUND(reader->arr) ? 
+ 										ARR_LBOUND(reader->arr) + ARR_NDIM(reader->arr) - reader->slice_ndim : NULL,
+ 														CurrentMemoryContext, true);
+ 		}
+ 		else
+ 		{
+ 			/* Return just one element. */
+ 			if (array_get_isnull(reader->nulls_ptr, reader->current_item++))
+ 			{
+ 				*isnull = true;
+ 				*value = (Datum) 0;
+ 			}
+ 			else
+ 			{
+ 				*isnull = false;
+ 				*value = fetch_att(reader->data_ptr, reader->elmbyval, reader->elmlen);
+ 				reader->data_ptr = att_addlength_pointer(reader->data_ptr, reader->elmlen, reader->data_ptr);
+ 				reader->data_ptr = (char *) att_align_nominal(reader->data_ptr, reader->elmalign);
+ 			}
+ 		}
+ 
+ 		return true;
+ 	}
+ 
+ 	return false;
+ }
+ 
+ /* ----------
+  * exec_stmt_foreach_a			Implements loop over array
+  *
+  * ----------
+  */
+ static int
+ exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
+ {
+ 	Datum		value;
+ 	bool		isnull;
+ 	Oid		valtype;
+ 	ArrayType	*arr;
+ 	PLpgSQL_datum		*ctrl_var = NULL;		/* be compiler quite */
+ 	Oid			ctrl_var_type = InvalidOid;
+ 	bool				found = false;
+ 	int				rc = PLPGSQL_RC_OK;
+ 	ArrayReader				reader;
+ 
+ 	/* get the value of the array expression using array_expr */
+ 	value = exec_eval_expr(estate, stmt->expr, &isnull, &valtype);
+ 	if (isnull)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("NULL value isn't allowed as parameter of FOREACH-IN")));
+ 
+ 	/* check the type of the expression - must be an array */
+ 	if (!OidIsValid(get_element_type(valtype)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("result of expression isn't an array"),
+ 				 errdetail("result of expression is %s",
+ 									format_type_be(valtype))));
+ 
+ 	/* make a copy of the result */
+ 	arr = DatumGetArrayTypePCopy(value);
+ 
+ 	/* Clean up any leftover temporary memory */
+ 	exec_eval_cleanup(estate);
+ 
+ 	/* slice dimension should be less or equal to array dimension */
+ 	if (stmt->slice > ARR_NDIM(arr))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("slice level %d is higher than array dimension",
+ 								    stmt->slice)));
+ 
+  	/*
+ 	 * If the target needs to be an array, check that it actually is,
+ 	 * and that it has a valid dimension for the array we're looping
+ 	 * through.
+ 	 */
+ 	if (stmt->slice > 0)
+ 	{
+ 		if (stmt->var != NULL)
+ 		{
+ 			int	typoid = stmt->var->datatype->typoid;
+ 			int	elmoid = get_element_type(typoid);
+ 
+ 			/* target variable has to be an array */
+ 			if (elmoid == InvalidOid)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("target variable \"%s\" for sliced array should be an array type",
+ 									stmt->var->refname),
+ 						 errhint("Value assigned will be of an array type.")));
+ 			/* Determine the number of items in the slice */
+ 			ctrl_var = estate->datums[stmt->var->dno];
+ 			ctrl_var_type = valtype;
+ 		}
+ 		else
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("target variable \"%s\" for  sliced array should be an array type",
+ 								stmt->row ? stmt->row->refname : stmt->rec->refname),
+ 					 errhint("Value assigned will be of an array type.")));
+ 	}
+ 	else
+ 	{
+ 		/* target variable has to be a scalar */
+ 		if (stmt->var != NULL)
+ 		{
+ 			int	typoid = stmt->var->datatype->typoid;
+ 			int	elmoid = get_element_type(typoid);
+ 
+ 			/* target variable should be a scalar */
+ 			if (elmoid != InvalidOid)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 						 errmsg("target variable \"%s\" is  an array",
+ 									stmt->var->refname),
+ 						errhint("Value assigned will be of a scalar type")));
+ 
+ 			ctrl_var = estate->datums[stmt->var->dno];
+ 		}
+ 		else if (stmt->row != NULL)
+ 		{
+ 			ctrl_var = estate->datums[stmt->row->dno];
+ 		}
+ 		else
+ 		{
+ 			ctrl_var = estate->datums[stmt->rec->dno];
+ 		}
+ 
+ 		/* without slicing a control value is same type as array element */
+ 		ctrl_var_type = ARR_ELEMTYPE(arr);
+ 	}
+ 
+ 	/* Create a reader (iterator) for array */
+ 	reader = create_array_reader(arr, stmt->slice);
+ 
+ 	/*
+ 	 * process all items in an array
+ 	 */
+ 	while (read_from_array(reader, &value, &isnull))
+ 	{
+ 		/* Fill a control variable */
+ 		exec_assign_value(estate, ctrl_var, value, ctrl_var_type, &isnull);
+ 
+ 		/* There is minimally one loop, therefor assign "found" to true */
+ 		found = true;
+ 
+ 		/*
+ 		 * Execute the statements
+ 		 */
+ 		rc = exec_stmts(estate, stmt->body);
+ 
+ 		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;
+ 			}
+ 		}
+ 	}
+ 
+ 	pfree(arr);
+ 	pfree(reader);
+ 
+ 	/*
+ 	 * 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
***************
*** 3179,3185 ****
  	SPI_cursor_close(portal);
  
  	return rc;
! }
  
  
  /* ----------
--- 3483,3489 ----
  	SPI_cursor_close(portal);
  
  	return rc;
! } 
  
  
  /* ----------
*** ./src/pl/plpgsql/src/pl_funcs.c.orig	2011-01-30 10:04:52.183014216 +0100
--- ./src/pl/plpgsql/src/pl_funcs.c	2011-01-30 10:05:21.418882684 +0100
***************
*** 230,235 ****
--- 230,237 ----
  			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:
***************
*** 293,298 ****
--- 295,301 ----
  static void dump_close(PLpgSQL_stmt_close *stmt);
  static void dump_perform(PLpgSQL_stmt_perform *stmt);
  static void dump_expr(PLpgSQL_expr *expr);
+ static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
  
  
  static void
***************
*** 376,381 ****
--- 379,387 ----
  		case PLPGSQL_STMT_PERFORM:
  			dump_perform((PLpgSQL_stmt_perform *) stmt);
  			break;
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			dump_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
  			break;
***************
*** 596,601 ****
--- 602,625 ----
  }
  
  static void
+ dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+ {
+ 	dump_ind();
+ 	printf("FOREACHA %s", (stmt->rec != NULL) ? stmt->rec->refname :
+ 						    (stmt->row != NULL) ? stmt->row->refname : stmt->var->refname);
+ 	if (stmt->slice != 0)
+ 		printf("SLICE %d ", stmt->slice);
+ 	printf("IN ");
+ 	dump_expr(stmt->expr);
+ 	printf("\n");
+ 
+ 	dump_stmts(stmt->body);
+ 
+ 	dump_ind();
+ 	printf("    ENDOFOREACHA");
+ }
+ 
+ static void
  dump_open(PLpgSQL_stmt_open *stmt)
  {
  	dump_ind();
*** ./src/pl/plpgsql/src/plpgsql.h.orig	2011-01-30 10:04:52.199016334 +0100
--- ./src/pl/plpgsql/src/plpgsql.h	2011-01-30 10:05:21.420882950 +0100
***************
*** 87,92 ****
--- 87,93 ----
  	PLPGSQL_STMT_CASE,
  	PLPGSQL_STMT_LOOP,
  	PLPGSQL_STMT_WHILE,
+ 	PLPGSQL_STMT_FOREACH_A,
  	PLPGSQL_STMT_FORI,
  	PLPGSQL_STMT_FORS,
  	PLPGSQL_STMT_FORC,
***************
*** 427,432 ****
--- 428,447 ----
  
  
  typedef struct
+ {								/* FOREACH item in array loop */
+ 	int			cmd_type;
+ 	int			lineno;
+ 	char	   *label;
+ 	PLpgSQL_var *var;
+ 	PLpgSQL_rec *rec;
+ 	PLpgSQL_row *row;
+ 	PLpgSQL_expr	*expr;
+ 	int	   slice;
+ 	List	   *body;			/* List of statements */
+ } PLpgSQL_stmt_foreach_a;
+ 
+ 
+ typedef struct
  {								/* FOR statement with integer loopvar	*/
  	int			cmd_type;
  	int			lineno;
*** ./src/pl/plpgsql/src/pl_scanner.c.orig	2011-01-30 10:04:52.217018714 +0100
--- ./src/pl/plpgsql/src/pl_scanner.c	2011-01-30 10:05:21.425883612 +0100
***************
*** 77,82 ****
--- 77,83 ----
  	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)
***************
*** 133,138 ****
--- 134,140 ----
  	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)
*** ./src/test/regress/expected/plpgsql.out.orig	2011-01-30 10:04:52.246022551 +0100
--- ./src/test/regress/expected/plpgsql.out	2011-01-30 10:05:21.479890757 +0100
***************
*** 4240,4242 ****
--- 4240,4436 ----
  (1 row)
  
  drop function unreserved_test();
+ -- Checking a FOREACH statement
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $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 $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ -- should fail
+ select foreach_test(ARRAY[1,2,3,4]);
+ ERROR:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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 $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 $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ -- should fail
+ select foreach_test(ARRAY[1,2,3,4]);
+ ERROR:  slice level 2 is higher than array dimension
+ 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 $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 $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 $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;
*** ./src/test/regress/sql/plpgsql.sql.orig	2011-01-30 10:04:52.275026388 +0100
--- ./src/test/regress/sql/plpgsql.sql	2011-01-30 10:05:21.489892080 +0100
***************
*** 3375,3377 ****
--- 3375,3488 ----
  select unreserved_test();
  
  drop function unreserved_test();
+ 
+ -- Checking a FOREACH statement
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $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 $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 $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 $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 $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 $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 $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;
#2Stephen Frost
sfrost@snowman.net
In reply to: Pavel Stehule (#1)
1 attachment(s)
Re: updated patch for foreach stmt

Greetings,

* Pavel Stehule (pavel.stehule@gmail.com) wrote:

I resend a patch with last update of this patch

Alright, so, like I said, I really like this feature and would like to
see it included. To that end, I've done perhaps a bit more than a
review of the patch. Pavel, if you could go over the changes I've made
and review them and let me know if you see any problems, I'd appreciate
it. I've tried to get it ready for a committer as much as I can without
being one. :)

I moved the array iteration over into arrayfuncs.c, cleaned it up quite
a bit, cleaned up the pl/pgsql foreach function, improved the PL/PgSQL
documentation to understand FOREACH as another top-level command, added
comments all over the place, etc.

Passes all regressions too.

commit 19deaf69a4dabfa4a223a6dcd36570866ad0bd3c
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 15:15:48 2011 -0500

PL/PgSQL FOREACH cleanup

Define and rename element OID to be more consistant, ensure
that the right name gets returned in error messages, and fix
regression output to match new error message (grammar cleanup).

commit f88fd2ab5419f9a2784677038b3fb01053c69163
Merge: f191af1 8c6e3ad
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 14:28:18 2011 -0500

Merge branch 'master' of git://git.postgresql.org/git/postgresql into plpgsql_foreach

commit f191af16f9d3e5ae0072e61c1b58713040cc8d64
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 14:27:05 2011 -0500

PL/PgSQL FOREACH Minor Whitespace Cleanup

commit 612cf5485f202a49aec70cf32f74d19d0d130b6b
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 14:06:06 2011 -0500

Improving FOREACH, code and documentation

This patch moves and reworks much of the array iteration code
that FOREACH had been implemented with to be part of arrayfuncs.c
and exported through utils/array.h. It also cleans up the error
handling and set up pieces of the FOREACH handling in pl_exec.c
Lastly, the documentation and comments are updated and improved.

commit 89058b79e43311e8f37af16c3fc17b622dc97578
Author: Stephen Frost <sfrost@snowman.net>
Date: Sun Feb 6 14:14:04 2011 -0500

Add FOREACH top-level PL/PgSQL command

This patch adds a new top-level PL/PgSQL command called FOREACH which
is intended to be for iterating over multi-value variables. This also
includes the first FOREACH type, an ARRAY iteration capability.

Patch by Pavel Stehule.

Thanks,

Stephen

Attachments:

plpgsql_foreach_20110208.patchtext/x-diff; charset=us-asciiDownload
*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 300,310 **** $$ LANGUAGE plpgsql;
      <para>
       All variables used in a block must be declared in the
       declarations section of the block.
!      (The only exceptions are that the loop variable of a <literal>FOR</> loop
!      iterating over a range of integer values is automatically declared as an
!      integer variable, and likewise the loop variable of a <literal>FOR</> loop
!      iterating over a cursor's result is automatically declared as a
!      record variable.)
      </para>
  
      <para>
--- 300,308 ----
      <para>
       All variables used in a block must be declared in the
       declarations section of the block.
!      (The only exceptions are the loop variables of <literal>FOR</> and
!      <literal>FOREACH</> loops which are automatically declared as the
!      appropriate variable type to match what to loop is over.)
      </para>
  
      <para>
***************
*** 1359,1375 **** GET DIAGNOSTICS integer_var = ROW_COUNT;
  
            <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).
!             <literal>FOUND</literal> is set this way when the
!             <command>FOR</> 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
!             execution of other statements within the loop body.
             </para>
            </listitem>
            <listitem>
--- 1357,1375 ----
  
            <listitem>
             <para>
!             A <command>FOR</> or <command>FOREACH</> statement sets
!             <literal>FOUND</literal> to true if it iterates one or more times,
!             else to 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) and all variants of the
!             <command>FOREACH</> statement (currently only ARRAY
!             <command>FOREACH</> loops).  <literal>FOUND</literal> is set this
!             way when the <command>FOR</> or <command>FOREACH</> loop exits;
!             inside the execution of the loop, <literal>FOUND</literal> is not
!             modified by the <command>FOR</> or <command>FOREACH</> statement,
!             although it might be changed by the execution of other statements
!             within the loop body.
             </para>
            </listitem>
            <listitem>
***************
*** 1910,1918 **** 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.
      </para>
  
      <sect3>
--- 1910,1918 ----
  
      <para>
       With the <literal>LOOP</>, <literal>EXIT</>,
!      <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,2243 **** END LOOP <optional> <replaceable>label</replaceable> </optional>;
--- 2238,2285 ----
      </para>
     </sect2>
  
+    <sect2 id="plpgsql-array-foreach-loop">
+     <title>Looping Through Arrays</title>
+ 
+     <para>
+     Similar to a <literal>FOR</> loop is the <literal>FOREACH</> loop.
+     <literal>FOREACH</> is used to loop over multi-value variables, such
+     as ARRAYs.  Other multi-value variables may be added to <literal>FOREACH</>
+     later.  Note that <literal>FOREACH</> can be thought of horizantally
+     looping, whereas <literal>FOR</> can be thought of a vertical loop.
+     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> SCALE <replaceable>number</replaceable> </optional> IN <replaceable>expression</replaceable> LOOP
+     <replaceable>statements</replaceable>
+ END LOOP <optional> <replaceable>label</replaceable> </optional>;
+ </synopsis>
+ 
+     This <literal>FOREACH</> form allows looping over each variable in an
+     array, in which case the loop variable will be the array's component
+     type (eg: the single-value loop variable for an integer ARRAY would
+     be an integer), or looping over segments of the array, in which case
+     the loop variable will be an array itself of the same type as the array.
+     Following is an example of looping through an integer array:
+ 
+ <programlisting>
+ CREATE FUNCTION sum(VARIADIC int[]) RETURNS int8 AS $$
+ DECLARE
+   s int8; x int;
+ BEGIN
+   FOREACH x IN $1
+   LOOP
+     s := s + x;
+   END LOOP;
+   RETURN s;
+ END;
+ $$ LANGUAGE plpgsql;
+ </programlisting>
+     </para>
+ 
+    </sect2>
+ 
     <sect2 id="plpgsql-error-trapping">
      <title>Trapping Errors</title>
  
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
***************
*** 68,74 **** static void CopyArrayEls(ArrayType *array,
  			 Datum *values, bool *nulls, int nitems,
  			 int typlen, bool typbyval, char typalign,
  			 bool freedata);
- static bool array_get_isnull(const bits8 *nullbitmap, int offset);
  static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull);
  static Datum ArrayCast(char *value, bool byval, int len);
  static int ArrayCastAndSet(Datum src,
--- 68,73 ----
***************
*** 3829,3834 **** arraycontained(PG_FUNCTION_ARGS)
--- 3828,3996 ----
  	PG_RETURN_BOOL(result);
  }
  
+ /*-----------------------------------------------------------------------------
+  * array iteration functions
+  *		These functions are used to efficiently iterate through arrays
+  *-----------------------------------------------------------------------------
+  */
+ 
+ /*
+  * Create an iterator for the array 'arr' which will allow us to
+  * step through the array one slice at a time (where a slice
+  * is the size of the dimension indicated by slice_ndim).
+  */
+ ArrayReader
+ array_create_iterator(ArrayType *arr, int slice_ndim)
+ {
+ 	ArrayReader reader = palloc(sizeof(ArrayReaderData));
+ 
+ 	/*
+ 	 * Store a pointer to the array itself, since we need
+ 	 * various information from it as we build up arrays
+ 	 * based off of it to return.  No need to have the
+ 	 * caller pass it in every time.
+ 	 */
+ 	reader->arr = arr;
+ 
+ 	/*
+ 	 * Initialize our data pointer to the beginning of the data
+ 	 * segment, this pointer gets moved along as we move through
+ 	 * the array.
+ 	 */
+ 	reader->data_ptr = ARR_DATA_PTR(arr);
+ 
+ 	/*
+ 	 * Similairly, initialize our current_item, this is used to
+ 	 * check against the null bitmap for each element as we walk
+ 	 * through the array.
+ 	 */
+ 	reader->current_item = 0;
+ 
+ 	/*
+ 	 * Get a pointer into the ARR_DIMS() array which starts where
+ 	 * a given slice would start at, so that we have the dimensions
+ 	 * of the slice itself when we're building an array to return.
+ 	 */
+ 	reader->slice_dims_offset = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim;
+ 
+ 	/*
+ 	 * Similairly, get a pointer into the ARR_LBOUND() which starts
+ 	 * where a given slice would start at.
+ 	 */
+ 	reader->slice_lbound_offset = ARR_LBOUND(arr) == 0 ?  NULL :
+ 								  ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim;
+ 
+ 	/*
+ 	 * Gather this information during the set-up phase, since
+ 	 * it involves a cache lookup and there's no sense doing
+ 	 * that over and over.
+ 	 */
+ 	get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ 						&reader->typlen,
+ 						&reader->typbyval,
+ 						&reader->typalign);
+ 
+ 	/* Get the total number of elements in the array */
+ 	reader->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ 
+ 	reader->slice_len = ArrayGetNItems(slice_ndim, reader->slice_dims_offset);
+ 	reader->slice_ndim = slice_ndim;
+ 
+ 	return reader;
+ }
+ 
+ /*
+  * Iterate one slice at a time through the array which was passed to
+  * array_create_iterator based on the slice size requested, returning
+  * each slice via *value / *isnull
+  */
+ bool
+ array_iterate(ArrayReader reader, Datum *value, bool *isnull)
+ {
+ 	/* Check if we have reached the end of the array */
+ 	if (reader->current_item >= reader->nitems)
+ 		return false;
+ 
+ 	/*
+ 	 * When slice_len is zero, we are just stepping through the array
+ 	 * one element at a time.  This is the nice, simple, case.
+ 	 */
+ 	if (reader->slice_len == 0)
+ 	{
+ 		/* Return just one element. */
+ 		if (array_get_isnull(ARR_NULLBITMAP(reader->arr), reader->current_item++))
+ 		{
+ 			*isnull = true;
+ 			*value = (Datum) 0;
+ 		}
+ 		else
+ 		{
+ 			/* non-NULL, so fetch the individual Datum to return */
+ 			*isnull = false;
+ 			*value = fetch_att(reader->data_ptr, reader->typbyval,
+ 							   reader->typlen);
+ 
+ 			/* Move our data pointer forward to the next element */
+ 			reader->data_ptr = att_addlength_pointer(reader->data_ptr,
+ 													 reader->typlen,
+ 													 reader->data_ptr);
+ 			reader->data_ptr = (char *) att_align_nominal(reader->data_ptr,
+ 														  reader->typalign);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * slice_len is non-zero, so we have to build and return an array which
+ 		 * matches the slice size requested (which is the size of one of the
+ 		 * dimensions of the array we're working on).
+ 		 */
+ 
+ 		int					i;
+ 		ArrayBuildState	    *astate = NULL;
+ 
+ 		/* Returns a slice. Copy "slice_len" elements to new array. */
+ 		for (i = 0; i < reader->slice_len; i++)
+ 		{
+ 			if (array_get_isnull(ARR_NULLBITMAP(reader->arr), reader->current_item++))
+ 			{
+ 				/* Add in a NULL element, if we come across one */
+ 				astate = accumArrayResult(astate, (Datum) 0, true,
+ 										ARR_ELEMTYPE(reader->arr),
+ 										CurrentMemoryContext);
+ 			}
+ 			else
+ 			{
+ 				Datum	element;
+ 
+ 				/* Fetch each element and add it into the new array */
+ 				element = fetch_att(reader->data_ptr, reader->typbyval,
+ 									reader->typlen);
+ 
+ 				astate = accumArrayResult(astate, element, false,
+ 										ARR_ELEMTYPE(reader->arr),
+ 										CurrentMemoryContext);
+ 
+ 				/* Move our data pointer forward to the next element */
+ 				reader->data_ptr = att_addlength_pointer(reader->data_ptr,
+ 														 reader->typlen,
+ 														 reader->data_ptr);
+ 				reader->data_ptr = (char *) att_align_nominal(reader->data_ptr,
+ 															  reader->typalign);
+ 			}
+ 		}
+ 
+ 		/* Build the final array to return */
+ 		*isnull = false;
+ 		*value = makeMdArrayResult(astate, reader->slice_ndim,
+ 								   reader->slice_dims_offset,
+ 								   reader->slice_lbound_offset,
+ 								   CurrentMemoryContext, true);
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /***************************************************************************/
  /******************|		  Support  Routines			  |*****************/
***************
*** 3840,3846 **** arraycontained(PG_FUNCTION_ARGS)
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! static bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
--- 4002,4008 ----
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
***************
*** 115,120 **** typedef struct ArrayMapState
--- 115,148 ----
  } ArrayMapState;
  
  /*
+  * array_create_iterator() will return the below structure which is later
+  * used and updated by array_iterate() to allow the caller to efficiently
+  * step through an array one slice at a time.  A slice is defined as one
+  * dimension of the base array.
+  */
+ typedef struct
+ {
+ 	/* 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 */
+ 
+ 	/* Cache'd info about the array that is pulled during initialization */
+ 	ArrayType	   *arr;			/* array we're iterating through */
+ 	int				nitems;			/* total number of elements in array */
+ 	int16			typlen;			/* element's length */
+ 	bool			typbyval;		/* element's byval property */
+ 	char			typalign;		/* align type of element */
+ 
+ 	/* information about the requested slice */
+ 	int				slice_len;		/* number of elements per slice */
+ 	int				slice_ndim;		/* slice dimension */
+ 	int			   *slice_dims_offset; /* slice dims array */
+ 	int			   *slice_lbound_offset; /* slice lbound array */
+ } ArrayReaderData;
+ 
+ typedef ArrayReaderData *ArrayReader;
+ 
+ /*
   * fmgr macros for array objects
   */
  #define DatumGetArrayTypeP(X)		  ((ArrayType *) PG_DETOAST_DATUM(X))
***************
*** 215,220 **** extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
--- 243,249 ----
  extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
  		  Datum dataValue, bool isNull,
  		  int arraytyplen, int elmlen, bool elmbyval, char elmalign);
+ extern bool array_get_isnull(const bits8 *nullbitmap, int offset);
  extern ArrayType *array_get_slice(ArrayType *array, int nSubscripts,
  				int *upperIndx, int *lowerIndx,
  				int arraytyplen, int elmlen, bool elmbyval, char elmalign);
***************
*** 254,259 **** extern Datum makeArrayResult(ArrayBuildState *astate,
--- 283,291 ----
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
  				  int *dims, int *lbs, MemoryContext rcontext, bool release);
  
+ extern ArrayReader array_create_iterator(ArrayType *arr, int slice_ndim);
+ extern bool array_iterate(ArrayReader reader, Datum *value, bool *isnull);
+ 
  /*
   * prototypes for functions defined in arrayutils.c
   */
*** a/src/pl/plpgsql/src/gram.y
--- b/src/pl/plpgsql/src/gram.y
***************
*** 21,26 ****
--- 21,27 ----
  #include "parser/parse_type.h"
  #include "parser/scanner.h"
  #include "parser/scansup.h"
+ #include "utils/array.h"
  
  
  /* Location tracking support --- simpler than bison's default */
***************
*** 134,139 **** static	List			*read_raise_options(void);
--- 135,141 ----
  			PLpgSQL_datum   *scalar;
  			PLpgSQL_rec     *rec;
  			PLpgSQL_row     *row;
+ 			int	slice;
  		}						forvariable;
  		struct
  		{
***************
*** 178,184 **** static	List			*read_raise_options(void);
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
--- 180,186 ----
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable foreach_control
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
***************
*** 190,196 **** 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 <list>	proc_exceptions
  %type <exception_block> exception_sect
--- 192,198 ----
  %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 stmt_foreach_a
  
  %type <list>	proc_exceptions
  %type <exception_block> exception_sect
***************
*** 264,269 **** static	List			*read_raise_options(void);
--- 266,272 ----
  %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,303 **** static	List			*read_raise_options(void);
--- 301,307 ----
  %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,744 **** proc_stmt		: pl_block ';'
--- 743,750 ----
  						{ $$ = $1; }
  				| stmt_for
  						{ $$ = $1; }
+ 				| stmt_foreach_a
+ 						{ $$ = $1; }
  				| stmt_exit
  						{ $$ = $1; }
  				| stmt_return
***************
*** 1386,1391 **** for_variable	: T_DATUM
--- 1392,1455 ----
  					}
  				;
  
+ stmt_foreach_a		: opt_block_label K_FOREACH foreach_control K_IN expr_until_loop loop_body
+ 					{
+ 						PLpgSQL_stmt_foreach_a *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->expr = $5;
+ 						new->slice = $3.slice;
+ 						new->body = $6.stmts;
+ 
+ 						if ($3.rec)
+ 						{
+ 							new->rec = $3.rec;
+ 							check_assignable((PLpgSQL_datum *) new->rec, @3);
+ 						}
+ 						else if ($3.row)
+ 						{
+ 							new->row = $3.row;
+ 							check_assignable((PLpgSQL_datum *) new->row, @3);
+ 						}
+ 						else if ($3.scalar)
+ 						{
+ 							Assert($3.scalar->dtype == PLPGSQL_DTYPE_VAR);
+ 							new->var = (PLpgSQL_var *) $3.scalar;
+ 							check_assignable($3.scalar, @3);
+ 
+ 						}
+ 						else
+ 						{
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_SYNTAX_ERROR),
+ 									 errmsg("loop variable of loop over array must be a record, row variable, scalar variable or list of scalar variables"),
+ 											 parser_errposition(@3)));
+ 						}
+ 
+ 						check_labels($1, $6.end_label, $6.end_label_location);
+ 						$$ = (PLpgSQL_stmt *) new;
+ 					}
+ 				;
+ 
+ foreach_control		: for_variable
+ 					{
+ 						$$ = $1;
+ 						$$.slice = 0;
+ 					}
+ 				| for_variable K_SLICE ICONST
+ 					{
+ 						$$ = $1;
+ 						$$.slice = $3;
+ 						if ($3 < 0 || $3 >= MAXDIM)
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 									 errmsg("number of slicing array dimensions (%d) exceeds the maximum allowed (%d)",
+ 												$3, MAXDIM),
+ 											 parser_errposition(@3)));
+ 					}
+ 				;
+ 
  stmt_exit		: exit_type opt_label opt_exitcond
  					{
  						PLpgSQL_stmt_exit *new;
***************
*** 2063,2068 **** unreserved_keyword	:
--- 2127,2133 ----
  				| K_ROW_COUNT
  				| K_ROWTYPE
  				| K_SCROLL
+ 				| K_SLICE
  				| K_SQLSTATE
  				| K_TYPE
  				| K_USE_COLUMN
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 107,112 **** static int exec_stmt_fors(PLpgSQL_execstate *estate,
--- 107,114 ----
  			   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,1317 **** exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
--- 1314,1323 ----
  			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;
***************
*** 2026,2031 **** exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
--- 2032,2231 ----
  	return rc;
  }
  
+ /* ----------
+  * exec_stmt_foreach_a			Implements loop over array
+  *
+  * This allows iterating over an array using either a single-step
+  * approach, or by doing it in slices where a slice is one dimension
+  * of the array.  When single-step'ing, the loop variable is the same
+  * type that the array stores (eg: integer), when looping through
+  * slices, the loop variable is, itself, 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)
+ {
+ 	/* variable which will be changing as we loop over the array */
+ 	Datum				value;
+ 	bool				isnull;
+ 
+ 	ArrayType		   *arr;					/* Our copy of the array */
+ 	Oid					valtype;				/* array value type */
+ 	Oid					elmOid;
+ 
+ 	/* control variable */
+ 	PLpgSQL_datum	   *ctrl_var = NULL;		/* make compiler quiet */
+ 	Oid					ctrl_var_type = InvalidOid;
+ 
+ 	/* return variables */
+ 	bool				found = false;
+ 	int					rc = PLPGSQL_RC_OK;
+ 
+ 	/* Our iterator for looping over the array */
+ 	ArrayReader			array_iterator;
+ 
+ 	/* get the value of the array expression using array_expr */
+ 	value = exec_eval_expr(estate, stmt->expr, &isnull, &valtype);
+ 	if (isnull)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("NULL value isn't allowed as parameter of FOREACH-IN")));
+ 
+ 	/* check the type of the expression - must be an array */
+ 	if (!OidIsValid(get_element_type(valtype)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("result of expression isn't an array"),
+ 				 errdetail("result of expression is %s",
+ 									format_type_be(valtype))));
+ 
+ 	/* make a copy of the array, is this necessary? */
+ 	arr = DatumGetArrayTypePCopy(value);
+ 
+ 	/* Clean up any leftover temporary memory */
+ 	exec_eval_cleanup(estate);
+ 
+ 	/*
+ 	 * If the target needs to be an array, check that it actually is,
+ 	 * and that it has a valid dimension for the array we're looping
+ 	 * through.
+ 	 */
+ 
+ 	/* Attempt to get the element OID, for error checking below */
+ 	elmOid = InvalidOid;
+ 	if (stmt->var != NULL)
+ 		elmOid = get_element_type(stmt->var->datatype->typoid);
+ 
+ 	/* slice dimension should be less or equal to array dimension */
+ 	if (stmt->slice > ARR_NDIM(arr))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("slice dimension requested (%d) is higher than the number of array dimensions (%d)",
+ 								    stmt->slice, ARR_NDIM(arr))));
+ 
+ 	/* Trying to do a slice, but we weren't given an array variable */
+ 	if (stmt->slice > 0 && elmOid == InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("target variable \"%s\" for sliced array should be an array type",
+ 							stmt->var ? stmt->var->refname :
+ 							stmt->row ? stmt->row->refname :
+ 							stmt->rec ? stmt->rec->refname : "Unknown"),
+ 				 errhint("Value assigned will be of an array type.")));
+ 
+ 	/* Trying to loop through as a scalar but given an array variable */
+ 	if (stmt->slice == 0 && stmt->var != NULL && elmOid != InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("target variable \"%s\" is  an array",
+ 							stmt->var->refname),
+ 				errhint("Value assigned will be of a scalar type")));
+ 
+ 	/* Set up the control variable/variable type */
+ 	if (stmt->slice > 0)
+ 	{
+ 		/* Doing array-based slices, set up our control variable */
+ 		ctrl_var_type = valtype;
+ 		ctrl_var = estate->datums[stmt->var->dno];
+ 	}
+ 	else
+ 	{
+ 		/* Looping one element at a time */
+ 
+ 		/* without slicing, loop var is the same type as an array element */
+ 		ctrl_var_type = ARR_ELEMTYPE(arr);
+ 
+ 		/* Figure out what kind of variable the element is going to be */
+ 		if (stmt->var != NULL)
+ 			ctrl_var = estate->datums[stmt->var->dno];
+ 		else if (stmt->row != NULL)
+ 			ctrl_var = estate->datums[stmt->row->dno];
+ 		else
+ 			ctrl_var = estate->datums[stmt->rec->dno];
+ 	}
+ 
+ 	/* Create an iterator to step through the array, a slice at a time */
+ 	array_iterator = array_create_iterator(arr, stmt->slice);
+ 
+ 	/*
+ 	 * Iterate over the array, a slice at a time, and execute the statements
+ 	 * in the block with the variable set to the current element or slice
+ 	 */
+ 	while (array_iterate(array_iterator, &value, &isnull))
+ 	{
+ 		/* There is minimally one loop, so assign "found" to true */
+ 		found = true;
+ 
+ 		/* Fill in the control variable */
+ 		exec_assign_value(estate, ctrl_var, value, ctrl_var_type, &isnull);
+ 
+ 		/*
+ 		 * 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;
+ 			}
+ 		}
+ 	}
+ 
+ 	pfree(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
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 230,235 **** plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
--- 230,237 ----
  			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:
***************
*** 293,298 **** static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
--- 295,301 ----
  static void dump_close(PLpgSQL_stmt_close *stmt);
  static void dump_perform(PLpgSQL_stmt_perform *stmt);
  static void dump_expr(PLpgSQL_expr *expr);
+ static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
  
  
  static void
***************
*** 376,381 **** dump_stmt(PLpgSQL_stmt *stmt)
--- 379,387 ----
  		case PLPGSQL_STMT_PERFORM:
  			dump_perform((PLpgSQL_stmt_perform *) stmt);
  			break;
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			dump_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
  			break;
***************
*** 596,601 **** dump_forc(PLpgSQL_stmt_forc *stmt)
--- 602,625 ----
  }
  
  static void
+ dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+ {
+ 	dump_ind();
+ 	printf("FOREACHA %s", (stmt->rec != NULL) ? stmt->rec->refname :
+ 						    (stmt->row != NULL) ? stmt->row->refname : stmt->var->refname);
+ 	if (stmt->slice != 0)
+ 		printf("SLICE %d ", stmt->slice);
+ 	printf("IN ");
+ 	dump_expr(stmt->expr);
+ 	printf("\n");
+ 
+ 	dump_stmts(stmt->body);
+ 
+ 	dump_ind();
+ 	printf("    ENDOFOREACHA");
+ }
+ 
+ static void
  dump_open(PLpgSQL_stmt_open *stmt)
  {
  	dump_ind();
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 77,82 **** static const ScanKeyword reserved_keywords[] = {
--- 77,83 ----
  	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)
***************
*** 133,138 **** static const ScanKeyword unreserved_keywords[] = {
--- 134,140 ----
  	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)
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 87,92 **** enum PLpgSQL_stmt_types
--- 87,93 ----
  	PLPGSQL_STMT_CASE,
  	PLPGSQL_STMT_LOOP,
  	PLPGSQL_STMT_WHILE,
+ 	PLPGSQL_STMT_FOREACH_A,
  	PLPGSQL_STMT_FORI,
  	PLPGSQL_STMT_FORS,
  	PLPGSQL_STMT_FORC,
***************
*** 427,432 **** typedef struct
--- 428,447 ----
  
  
  typedef struct
+ {								/* FOREACH item in array loop */
+ 	int			cmd_type;
+ 	int			lineno;
+ 	char	   *label;
+ 	PLpgSQL_var *var;
+ 	PLpgSQL_rec *rec;
+ 	PLpgSQL_row *row;
+ 	PLpgSQL_expr	*expr;
+ 	int	   slice;
+ 	List	   *body;			/* List of statements */
+ } PLpgSQL_stmt_foreach_a;
+ 
+ 
+ typedef struct
  {								/* FOR statement with integer loopvar	*/
  	int			cmd_type;
  	int			lineno;
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 4240,4242 **** select unreserved_test();
--- 4240,4436 ----
  (1 row)
  
  drop function unreserved_test();
+ -- Checking a FOREACH statement
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $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 $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ -- should fail
+ select foreach_test(ARRAY[1,2,3,4]);
+ ERROR:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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 $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 $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ -- should fail
+ select foreach_test(ARRAY[1,2,3,4]);
+ ERROR:  slice dimension requested (2) is higher than the number of array dimensions (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 $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 $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 $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;
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 3375,3377 **** $$ language plpgsql;
--- 3375,3488 ----
  select unreserved_test();
  
  drop function unreserved_test();
+ 
+ -- Checking a FOREACH statement
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $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 $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 $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 $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 $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 $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 $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;
#3Robert Haas
robertmhaas@gmail.com
In reply to: Stephen Frost (#2)
Re: updated patch for foreach stmt

On Tue, Feb 8, 2011 at 3:26 PM, Stephen Frost <sfrost@snowman.net> wrote:

Greetings,

* Pavel Stehule (pavel.stehule@gmail.com) wrote:

I resend a patch with last update of this patch

Alright, so, like I said, I really like this feature and would like to
see it included.  To that end, I've done perhaps a bit more than a
review of the patch.  Pavel, if you could go over the changes I've made
and review them and let me know if you see any problems, I'd appreciate
it.  I've tried to get it ready for a committer as much as I can without
being one. :)

Amen to that!

I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr. That's probably a good
idea, because it gives us an escape hatch against needing to invent
yet another variant of this syntax - the word immediately following IN
can be known with confidence to be intended as a keyword rather than
as part of the expression.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#4Pavel Stehule
pavel.stehule@gmail.com
In reply to: Stephen Frost (#2)
Re: updated patch for foreach stmt

2011/2/8 Stephen Frost <sfrost@snowman.net>:

Greetings,

* Pavel Stehule (pavel.stehule@gmail.com) wrote:

I resend a patch with last update of this patch

Alright, so, like I said, I really like this feature and would like to
see it included.  To that end, I've done perhaps a bit more than a
review of the patch.  Pavel, if you could go over the changes I've made
and review them and let me know if you see any problems, I'd appreciate
it.  I've tried to get it ready for a committer as much as I can without
being one. :)

I moved the array iteration over into arrayfuncs.c, cleaned it up quite
a bit, cleaned up the pl/pgsql foreach function, improved the PL/PgSQL
documentation to understand FOREACH as another top-level command, added
comments all over the place, etc.

It's looking well - thank you.

There is only bad keywords in doc - SCALE instead SLICE and a maybe a
usage of slicing need a example.

It is nice.

Regards

Pavel Stehule

Show quoted text

Passes all regressions too.

commit 19deaf69a4dabfa4a223a6dcd36570866ad0bd3c
Author: Stephen Frost <sfrost@snowman.net>
Date:   Tue Feb 8 15:15:48 2011 -0500

   PL/PgSQL FOREACH cleanup

   Define and rename element OID to be more consistant, ensure
   that the right name gets returned in error messages, and fix
   regression output to match new error message (grammar cleanup).

commit f88fd2ab5419f9a2784677038b3fb01053c69163
Merge: f191af1 8c6e3ad
Author: Stephen Frost <sfrost@snowman.net>
Date:   Tue Feb 8 14:28:18 2011 -0500

   Merge branch 'master' of git://git.postgresql.org/git/postgresql into plpgsql_foreach

commit f191af16f9d3e5ae0072e61c1b58713040cc8d64
Author: Stephen Frost <sfrost@snowman.net>
Date:   Tue Feb 8 14:27:05 2011 -0500

   PL/PgSQL FOREACH Minor Whitespace Cleanup

commit 612cf5485f202a49aec70cf32f74d19d0d130b6b
Author: Stephen Frost <sfrost@snowman.net>
Date:   Tue Feb 8 14:06:06 2011 -0500

   Improving FOREACH, code and documentation

   This patch moves and reworks much of the array iteration code
   that FOREACH had been implemented with to be part of arrayfuncs.c
   and exported through utils/array.h.  It also cleans up the error
   handling and set up pieces of the FOREACH handling in pl_exec.c
   Lastly, the documentation and comments are updated and improved.

commit 89058b79e43311e8f37af16c3fc17b622dc97578
Author: Stephen Frost <sfrost@snowman.net>
Date:   Sun Feb 6 14:14:04 2011 -0500

   Add FOREACH top-level PL/PgSQL command

   This patch adds a new top-level PL/PgSQL command called FOREACH which
   is intended to be for iterating over multi-value variables.  This also
   includes the first FOREACH type, an ARRAY iteration capability.

   Patch by Pavel Stehule.

               Thanks,

                       Stephen

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)

iEYEARECAAYFAk1Rpu8ACgkQrzgMPqB3kiiuTQCfdY8Cwg5DVuvu2xKpcv6M7QQ1
+TwAnR5ZFXsGdAwgHwQEprcYIlp8t0wy
=DAjZ
-----END PGP SIGNATURE-----

#5Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#3)
1 attachment(s)
Re: updated patch for foreach stmt

* Robert Haas (robertmhaas@gmail.com) wrote:

Amen to that!

Hopefully it helped. :)

I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr. That's probably a good
idea, because it gives us an escape hatch against needing to invent
yet another variant of this syntax - the word immediately following IN
can be known with confidence to be intended as a keyword rather than
as part of the expression.

Alright, alright, *I* don't care that much, though I do feel it's a bit
excessive. Updated patch against HEAD attached.

commit a5d32fa41fbbbd9ace465f62be714366990061d4
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 15:57:40 2011 -0500

PL/PgSQL FOREACH - Add ARRAY keyword

Add ARRAY as required after IN when using FOREACH, to
future-proof against later kinds of FOREACH commands.

Thanks,

Stephen

Attachments:

plpgsql_foreach_2011020802.patchtext/x-diff; charset=us-asciiDownload
*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 300,310 **** $$ LANGUAGE plpgsql;
      <para>
       All variables used in a block must be declared in the
       declarations section of the block.
!      (The only exceptions are that the loop variable of a <literal>FOR</> loop
!      iterating over a range of integer values is automatically declared as an
!      integer variable, and likewise the loop variable of a <literal>FOR</> loop
!      iterating over a cursor's result is automatically declared as a
!      record variable.)
      </para>
  
      <para>
--- 300,308 ----
      <para>
       All variables used in a block must be declared in the
       declarations section of the block.
!      (The only exceptions are the loop variables of <literal>FOR</> and
!      <literal>FOREACH</> loops which are automatically declared as the
!      appropriate variable type to match what to loop is over.)
      </para>
  
      <para>
***************
*** 1359,1375 **** GET DIAGNOSTICS integer_var = ROW_COUNT;
  
            <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).
!             <literal>FOUND</literal> is set this way when the
!             <command>FOR</> 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
!             execution of other statements within the loop body.
             </para>
            </listitem>
            <listitem>
--- 1357,1375 ----
  
            <listitem>
             <para>
!             A <command>FOR</> or <command>FOREACH</> statement sets
!             <literal>FOUND</literal> to true if it iterates one or more times,
!             else to 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) and all variants of the
!             <command>FOREACH</> statement (currently only ARRAY
!             <command>FOREACH</> loops).  <literal>FOUND</literal> is set this
!             way when the <command>FOR</> or <command>FOREACH</> loop exits;
!             inside the execution of the loop, <literal>FOUND</literal> is not
!             modified by the <command>FOR</> or <command>FOREACH</> statement,
!             although it might be changed by the execution of other statements
!             within the loop body.
             </para>
            </listitem>
            <listitem>
***************
*** 1910,1918 **** 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.
      </para>
  
      <sect3>
--- 1910,1918 ----
  
      <para>
       With the <literal>LOOP</>, <literal>EXIT</>,
!      <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,2243 **** END LOOP <optional> <replaceable>label</replaceable> </optional>;
--- 2238,2285 ----
      </para>
     </sect2>
  
+    <sect2 id="plpgsql-array-foreach-loop">
+     <title>Looping Through Arrays</title>
+ 
+     <para>
+     Similar to a <literal>FOR</> loop is the <literal>FOREACH</> loop.
+     <literal>FOREACH</> is used to loop over multi-value variables, such
+     as ARRAYs.  Other multi-value variables may be added to <literal>FOREACH</>
+     later.  Note that <literal>FOREACH</> can be thought of horizantally
+     looping, whereas <literal>FOR</> can be thought of a vertical loop.
+     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> SCALE <replaceable>number</replaceable> </optional> IN <replaceable>expression</replaceable> LOOP
+     <replaceable>statements</replaceable>
+ END LOOP <optional> <replaceable>label</replaceable> </optional>;
+ </synopsis>
+ 
+     This <literal>FOREACH</> form allows looping over each variable in an
+     array, in which case the loop variable will be the array's component
+     type (eg: the single-value loop variable for an integer ARRAY would
+     be an integer), or looping over segments of the array, in which case
+     the loop variable will be an array itself of the same type as the array.
+     Following is an example of looping through an integer array:
+ 
+ <programlisting>
+ CREATE FUNCTION sum(VARIADIC int[]) RETURNS int8 AS $$
+ DECLARE
+   s int8; x int;
+ BEGIN
+   FOREACH x IN $1
+   LOOP
+     s := s + x;
+   END LOOP;
+   RETURN s;
+ END;
+ $$ LANGUAGE plpgsql;
+ </programlisting>
+     </para>
+ 
+    </sect2>
+ 
     <sect2 id="plpgsql-error-trapping">
      <title>Trapping Errors</title>
  
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
***************
*** 68,74 **** static void CopyArrayEls(ArrayType *array,
  			 Datum *values, bool *nulls, int nitems,
  			 int typlen, bool typbyval, char typalign,
  			 bool freedata);
- static bool array_get_isnull(const bits8 *nullbitmap, int offset);
  static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull);
  static Datum ArrayCast(char *value, bool byval, int len);
  static int ArrayCastAndSet(Datum src,
--- 68,73 ----
***************
*** 3829,3834 **** arraycontained(PG_FUNCTION_ARGS)
--- 3828,3996 ----
  	PG_RETURN_BOOL(result);
  }
  
+ /*-----------------------------------------------------------------------------
+  * array iteration functions
+  *		These functions are used to efficiently iterate through arrays
+  *-----------------------------------------------------------------------------
+  */
+ 
+ /*
+  * Create an iterator for the array 'arr' which will allow us to
+  * step through the array one slice at a time (where a slice
+  * is the size of the dimension indicated by slice_ndim).
+  */
+ ArrayReader
+ array_create_iterator(ArrayType *arr, int slice_ndim)
+ {
+ 	ArrayReader reader = palloc(sizeof(ArrayReaderData));
+ 
+ 	/*
+ 	 * Store a pointer to the array itself, since we need
+ 	 * various information from it as we build up arrays
+ 	 * based off of it to return.  No need to have the
+ 	 * caller pass it in every time.
+ 	 */
+ 	reader->arr = arr;
+ 
+ 	/*
+ 	 * Initialize our data pointer to the beginning of the data
+ 	 * segment, this pointer gets moved along as we move through
+ 	 * the array.
+ 	 */
+ 	reader->data_ptr = ARR_DATA_PTR(arr);
+ 
+ 	/*
+ 	 * Similairly, initialize our current_item, this is used to
+ 	 * check against the null bitmap for each element as we walk
+ 	 * through the array.
+ 	 */
+ 	reader->current_item = 0;
+ 
+ 	/*
+ 	 * Get a pointer into the ARR_DIMS() array which starts where
+ 	 * a given slice would start at, so that we have the dimensions
+ 	 * of the slice itself when we're building an array to return.
+ 	 */
+ 	reader->slice_dims_offset = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim;
+ 
+ 	/*
+ 	 * Similairly, get a pointer into the ARR_LBOUND() which starts
+ 	 * where a given slice would start at.
+ 	 */
+ 	reader->slice_lbound_offset = ARR_LBOUND(arr) == 0 ?  NULL :
+ 								  ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim;
+ 
+ 	/*
+ 	 * Gather this information during the set-up phase, since
+ 	 * it involves a cache lookup and there's no sense doing
+ 	 * that over and over.
+ 	 */
+ 	get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ 						&reader->typlen,
+ 						&reader->typbyval,
+ 						&reader->typalign);
+ 
+ 	/* Get the total number of elements in the array */
+ 	reader->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ 
+ 	reader->slice_len = ArrayGetNItems(slice_ndim, reader->slice_dims_offset);
+ 	reader->slice_ndim = slice_ndim;
+ 
+ 	return reader;
+ }
+ 
+ /*
+  * Iterate one slice at a time through the array which was passed to
+  * array_create_iterator based on the slice size requested, returning
+  * each slice via *value / *isnull
+  */
+ bool
+ array_iterate(ArrayReader reader, Datum *value, bool *isnull)
+ {
+ 	/* Check if we have reached the end of the array */
+ 	if (reader->current_item >= reader->nitems)
+ 		return false;
+ 
+ 	/*
+ 	 * When slice_len is zero, we are just stepping through the array
+ 	 * one element at a time.  This is the nice, simple, case.
+ 	 */
+ 	if (reader->slice_len == 0)
+ 	{
+ 		/* Return just one element. */
+ 		if (array_get_isnull(ARR_NULLBITMAP(reader->arr), reader->current_item++))
+ 		{
+ 			*isnull = true;
+ 			*value = (Datum) 0;
+ 		}
+ 		else
+ 		{
+ 			/* non-NULL, so fetch the individual Datum to return */
+ 			*isnull = false;
+ 			*value = fetch_att(reader->data_ptr, reader->typbyval,
+ 							   reader->typlen);
+ 
+ 			/* Move our data pointer forward to the next element */
+ 			reader->data_ptr = att_addlength_pointer(reader->data_ptr,
+ 													 reader->typlen,
+ 													 reader->data_ptr);
+ 			reader->data_ptr = (char *) att_align_nominal(reader->data_ptr,
+ 														  reader->typalign);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * slice_len is non-zero, so we have to build and return an array which
+ 		 * matches the slice size requested (which is the size of one of the
+ 		 * dimensions of the array we're working on).
+ 		 */
+ 
+ 		int					i;
+ 		ArrayBuildState	    *astate = NULL;
+ 
+ 		/* Returns a slice. Copy "slice_len" elements to new array. */
+ 		for (i = 0; i < reader->slice_len; i++)
+ 		{
+ 			if (array_get_isnull(ARR_NULLBITMAP(reader->arr), reader->current_item++))
+ 			{
+ 				/* Add in a NULL element, if we come across one */
+ 				astate = accumArrayResult(astate, (Datum) 0, true,
+ 										ARR_ELEMTYPE(reader->arr),
+ 										CurrentMemoryContext);
+ 			}
+ 			else
+ 			{
+ 				Datum	element;
+ 
+ 				/* Fetch each element and add it into the new array */
+ 				element = fetch_att(reader->data_ptr, reader->typbyval,
+ 									reader->typlen);
+ 
+ 				astate = accumArrayResult(astate, element, false,
+ 										ARR_ELEMTYPE(reader->arr),
+ 										CurrentMemoryContext);
+ 
+ 				/* Move our data pointer forward to the next element */
+ 				reader->data_ptr = att_addlength_pointer(reader->data_ptr,
+ 														 reader->typlen,
+ 														 reader->data_ptr);
+ 				reader->data_ptr = (char *) att_align_nominal(reader->data_ptr,
+ 															  reader->typalign);
+ 			}
+ 		}
+ 
+ 		/* Build the final array to return */
+ 		*isnull = false;
+ 		*value = makeMdArrayResult(astate, reader->slice_ndim,
+ 								   reader->slice_dims_offset,
+ 								   reader->slice_lbound_offset,
+ 								   CurrentMemoryContext, true);
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /***************************************************************************/
  /******************|		  Support  Routines			  |*****************/
***************
*** 3840,3846 **** arraycontained(PG_FUNCTION_ARGS)
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! static bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
--- 4002,4008 ----
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
***************
*** 115,120 **** typedef struct ArrayMapState
--- 115,148 ----
  } ArrayMapState;
  
  /*
+  * array_create_iterator() will return the below structure which is later
+  * used and updated by array_iterate() to allow the caller to efficiently
+  * step through an array one slice at a time.  A slice is defined as one
+  * dimension of the base array.
+  */
+ typedef struct
+ {
+ 	/* 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 */
+ 
+ 	/* Cache'd info about the array that is pulled during initialization */
+ 	ArrayType	   *arr;			/* array we're iterating through */
+ 	int				nitems;			/* total number of elements in array */
+ 	int16			typlen;			/* element's length */
+ 	bool			typbyval;		/* element's byval property */
+ 	char			typalign;		/* align type of element */
+ 
+ 	/* information about the requested slice */
+ 	int				slice_len;		/* number of elements per slice */
+ 	int				slice_ndim;		/* slice dimension */
+ 	int			   *slice_dims_offset; /* slice dims array */
+ 	int			   *slice_lbound_offset; /* slice lbound array */
+ } ArrayReaderData;
+ 
+ typedef ArrayReaderData *ArrayReader;
+ 
+ /*
   * fmgr macros for array objects
   */
  #define DatumGetArrayTypeP(X)		  ((ArrayType *) PG_DETOAST_DATUM(X))
***************
*** 215,220 **** extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
--- 243,249 ----
  extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
  		  Datum dataValue, bool isNull,
  		  int arraytyplen, int elmlen, bool elmbyval, char elmalign);
+ extern bool array_get_isnull(const bits8 *nullbitmap, int offset);
  extern ArrayType *array_get_slice(ArrayType *array, int nSubscripts,
  				int *upperIndx, int *lowerIndx,
  				int arraytyplen, int elmlen, bool elmbyval, char elmalign);
***************
*** 254,259 **** extern Datum makeArrayResult(ArrayBuildState *astate,
--- 283,291 ----
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
  				  int *dims, int *lbs, MemoryContext rcontext, bool release);
  
+ extern ArrayReader array_create_iterator(ArrayType *arr, int slice_ndim);
+ extern bool array_iterate(ArrayReader reader, Datum *value, bool *isnull);
+ 
  /*
   * prototypes for functions defined in arrayutils.c
   */
*** a/src/pl/plpgsql/src/gram.y
--- b/src/pl/plpgsql/src/gram.y
***************
*** 21,26 ****
--- 21,27 ----
  #include "parser/parse_type.h"
  #include "parser/scanner.h"
  #include "parser/scansup.h"
+ #include "utils/array.h"
  
  
  /* Location tracking support --- simpler than bison's default */
***************
*** 134,139 **** static	List			*read_raise_options(void);
--- 135,141 ----
  			PLpgSQL_datum   *scalar;
  			PLpgSQL_rec     *rec;
  			PLpgSQL_row     *row;
+ 			int	slice;
  		}						forvariable;
  		struct
  		{
***************
*** 178,184 **** static	List			*read_raise_options(void);
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
--- 180,186 ----
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable foreach_control
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
***************
*** 190,196 **** 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 <list>	proc_exceptions
  %type <exception_block> exception_sect
--- 192,198 ----
  %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 stmt_foreach_a
  
  %type <list>	proc_exceptions
  %type <exception_block> exception_sect
***************
*** 239,244 **** static	List			*read_raise_options(void);
--- 241,247 ----
  %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,269 **** static	List			*read_raise_options(void);
--- 267,273 ----
  %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,303 **** static	List			*read_raise_options(void);
--- 302,308 ----
  %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,744 **** proc_stmt		: pl_block ';'
--- 744,751 ----
  						{ $$ = $1; }
  				| stmt_for
  						{ $$ = $1; }
+ 				| stmt_foreach_a
+ 						{ $$ = $1; }
  				| stmt_exit
  						{ $$ = $1; }
  				| stmt_return
***************
*** 1386,1391 **** for_variable	: T_DATUM
--- 1393,1456 ----
  					}
  				;
  
+ stmt_foreach_a		: opt_block_label K_FOREACH foreach_control K_IN K_ARRAY expr_until_loop loop_body
+ 					{
+ 						PLpgSQL_stmt_foreach_a *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->expr = $6;
+ 						new->slice = $3.slice;
+ 						new->body = $7.stmts;
+ 
+ 						if ($3.rec)
+ 						{
+ 							new->rec = $3.rec;
+ 							check_assignable((PLpgSQL_datum *) new->rec, @3);
+ 						}
+ 						else if ($3.row)
+ 						{
+ 							new->row = $3.row;
+ 							check_assignable((PLpgSQL_datum *) new->row, @3);
+ 						}
+ 						else if ($3.scalar)
+ 						{
+ 							Assert($3.scalar->dtype == PLPGSQL_DTYPE_VAR);
+ 							new->var = (PLpgSQL_var *) $3.scalar;
+ 							check_assignable($3.scalar, @3);
+ 
+ 						}
+ 						else
+ 						{
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_SYNTAX_ERROR),
+ 									 errmsg("loop variable of loop over array must be a record, row variable, scalar variable or list of scalar variables"),
+ 											 parser_errposition(@3)));
+ 						}
+ 
+ 						check_labels($1, $7.end_label, $7.end_label_location);
+ 						$$ = (PLpgSQL_stmt *) new;
+ 					}
+ 				;
+ 
+ foreach_control		: for_variable
+ 					{
+ 						$$ = $1;
+ 						$$.slice = 0;
+ 					}
+ 				| for_variable K_SLICE ICONST
+ 					{
+ 						$$ = $1;
+ 						$$.slice = $3;
+ 						if ($3 < 0 || $3 >= MAXDIM)
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 									 errmsg("number of slicing array dimensions (%d) exceeds the maximum allowed (%d)",
+ 												$3, MAXDIM),
+ 											 parser_errposition(@3)));
+ 					}
+ 				;
+ 
  stmt_exit		: exit_type opt_label opt_exitcond
  					{
  						PLpgSQL_stmt_exit *new;
***************
*** 2035,2040 **** any_identifier	: T_WORD
--- 2100,2106 ----
  unreserved_keyword	:
  				K_ABSOLUTE
  				| K_ALIAS
+ 				| K_ARRAY
  				| K_BACKWARD
  				| K_CONSTANT
  				| K_CURSOR
***************
*** 2063,2068 **** unreserved_keyword	:
--- 2129,2135 ----
  				| K_ROW_COUNT
  				| K_ROWTYPE
  				| K_SCROLL
+ 				| K_SLICE
  				| K_SQLSTATE
  				| K_TYPE
  				| K_USE_COLUMN
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 107,112 **** static int exec_stmt_fors(PLpgSQL_execstate *estate,
--- 107,114 ----
  			   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,1317 **** exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
--- 1314,1323 ----
  			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;
***************
*** 2026,2031 **** exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
--- 2032,2231 ----
  	return rc;
  }
  
+ /* ----------
+  * exec_stmt_foreach_a			Implements loop over array
+  *
+  * This allows iterating over an array using either a single-step
+  * approach, or by doing it in slices where a slice is one dimension
+  * of the array.  When single-step'ing, the loop variable is the same
+  * type that the array stores (eg: integer), when looping through
+  * slices, the loop variable is, itself, 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)
+ {
+ 	/* variable which will be changing as we loop over the array */
+ 	Datum				value;
+ 	bool				isnull;
+ 
+ 	ArrayType		   *arr;					/* Our copy of the array */
+ 	Oid					valtype;				/* array value type */
+ 	Oid					elmOid;
+ 
+ 	/* control variable */
+ 	PLpgSQL_datum	   *ctrl_var = NULL;		/* make compiler quiet */
+ 	Oid					ctrl_var_type = InvalidOid;
+ 
+ 	/* return variables */
+ 	bool				found = false;
+ 	int					rc = PLPGSQL_RC_OK;
+ 
+ 	/* Our iterator for looping over the array */
+ 	ArrayReader			array_iterator;
+ 
+ 	/* get the value of the array expression using array_expr */
+ 	value = exec_eval_expr(estate, stmt->expr, &isnull, &valtype);
+ 	if (isnull)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("NULL value isn't allowed as parameter of FOREACH-IN")));
+ 
+ 	/* check the type of the expression - must be an array */
+ 	if (!OidIsValid(get_element_type(valtype)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("result of expression isn't an array"),
+ 				 errdetail("result of expression is %s",
+ 									format_type_be(valtype))));
+ 
+ 	/* make a copy of the array, is this necessary? */
+ 	arr = DatumGetArrayTypePCopy(value);
+ 
+ 	/* Clean up any leftover temporary memory */
+ 	exec_eval_cleanup(estate);
+ 
+ 	/*
+ 	 * If the target needs to be an array, check that it actually is,
+ 	 * and that it has a valid dimension for the array we're looping
+ 	 * through.
+ 	 */
+ 
+ 	/* Attempt to get the element OID, for error checking below */
+ 	elmOid = InvalidOid;
+ 	if (stmt->var != NULL)
+ 		elmOid = get_element_type(stmt->var->datatype->typoid);
+ 
+ 	/* slice dimension should be less or equal to array dimension */
+ 	if (stmt->slice > ARR_NDIM(arr))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("slice dimension requested (%d) is higher than the number of array dimensions (%d)",
+ 								    stmt->slice, ARR_NDIM(arr))));
+ 
+ 	/* Trying to do a slice, but we weren't given an array variable */
+ 	if (stmt->slice > 0 && elmOid == InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("target variable \"%s\" for sliced array should be an array type",
+ 							stmt->var ? stmt->var->refname :
+ 							stmt->row ? stmt->row->refname :
+ 							stmt->rec ? stmt->rec->refname : "Unknown"),
+ 				 errhint("Value assigned will be of an array type.")));
+ 
+ 	/* Trying to loop through as a scalar but given an array variable */
+ 	if (stmt->slice == 0 && stmt->var != NULL && elmOid != InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("target variable \"%s\" is  an array",
+ 							stmt->var->refname),
+ 				errhint("Value assigned will be of a scalar type")));
+ 
+ 	/* Set up the control variable/variable type */
+ 	if (stmt->slice > 0)
+ 	{
+ 		/* Doing array-based slices, set up our control variable */
+ 		ctrl_var_type = valtype;
+ 		ctrl_var = estate->datums[stmt->var->dno];
+ 	}
+ 	else
+ 	{
+ 		/* Looping one element at a time */
+ 
+ 		/* without slicing, loop var is the same type as an array element */
+ 		ctrl_var_type = ARR_ELEMTYPE(arr);
+ 
+ 		/* Figure out what kind of variable the element is going to be */
+ 		if (stmt->var != NULL)
+ 			ctrl_var = estate->datums[stmt->var->dno];
+ 		else if (stmt->row != NULL)
+ 			ctrl_var = estate->datums[stmt->row->dno];
+ 		else
+ 			ctrl_var = estate->datums[stmt->rec->dno];
+ 	}
+ 
+ 	/* Create an iterator to step through the array, a slice at a time */
+ 	array_iterator = array_create_iterator(arr, stmt->slice);
+ 
+ 	/*
+ 	 * Iterate over the array, a slice at a time, and execute the statements
+ 	 * in the block with the variable set to the current element or slice
+ 	 */
+ 	while (array_iterate(array_iterator, &value, &isnull))
+ 	{
+ 		/* There is minimally one loop, so assign "found" to true */
+ 		found = true;
+ 
+ 		/* Fill in the control variable */
+ 		exec_assign_value(estate, ctrl_var, value, ctrl_var_type, &isnull);
+ 
+ 		/*
+ 		 * 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;
+ 			}
+ 		}
+ 	}
+ 
+ 	pfree(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
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 230,235 **** plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
--- 230,237 ----
  			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:
***************
*** 293,298 **** static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
--- 295,301 ----
  static void dump_close(PLpgSQL_stmt_close *stmt);
  static void dump_perform(PLpgSQL_stmt_perform *stmt);
  static void dump_expr(PLpgSQL_expr *expr);
+ static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
  
  
  static void
***************
*** 376,381 **** dump_stmt(PLpgSQL_stmt *stmt)
--- 379,387 ----
  		case PLPGSQL_STMT_PERFORM:
  			dump_perform((PLpgSQL_stmt_perform *) stmt);
  			break;
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			dump_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
  			break;
***************
*** 596,601 **** dump_forc(PLpgSQL_stmt_forc *stmt)
--- 602,625 ----
  }
  
  static void
+ dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+ {
+ 	dump_ind();
+ 	printf("FOREACHA %s", (stmt->rec != NULL) ? stmt->rec->refname :
+ 						    (stmt->row != NULL) ? stmt->row->refname : stmt->var->refname);
+ 	if (stmt->slice != 0)
+ 		printf("SLICE %d ", stmt->slice);
+ 	printf("IN ");
+ 	dump_expr(stmt->expr);
+ 	printf("\n");
+ 
+ 	dump_stmts(stmt->body);
+ 
+ 	dump_ind();
+ 	printf("    ENDOFOREACHA");
+ }
+ 
+ static void
  dump_open(PLpgSQL_stmt_open *stmt)
  {
  	dump_ind();
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 77,82 **** static const ScanKeyword reserved_keywords[] = {
--- 77,83 ----
  	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,110 **** static const int num_reserved_keywords = lengthof(reserved_keywords);
--- 106,112 ----
  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,138 **** static const ScanKeyword unreserved_keywords[] = {
--- 135,141 ----
  	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)
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 87,92 **** enum PLpgSQL_stmt_types
--- 87,93 ----
  	PLPGSQL_STMT_CASE,
  	PLPGSQL_STMT_LOOP,
  	PLPGSQL_STMT_WHILE,
+ 	PLPGSQL_STMT_FOREACH_A,
  	PLPGSQL_STMT_FORI,
  	PLPGSQL_STMT_FORS,
  	PLPGSQL_STMT_FORC,
***************
*** 427,432 **** typedef struct
--- 428,447 ----
  
  
  typedef struct
+ {								/* FOREACH item in array loop */
+ 	int			cmd_type;
+ 	int			lineno;
+ 	char	   *label;
+ 	PLpgSQL_var *var;
+ 	PLpgSQL_rec *rec;
+ 	PLpgSQL_row *row;
+ 	PLpgSQL_expr	*expr;
+ 	int	   slice;
+ 	List	   *body;			/* List of statements */
+ } PLpgSQL_stmt_foreach_a;
+ 
+ 
+ typedef struct
  {								/* FOR statement with integer loopvar	*/
  	int			cmd_type;
  	int			lineno;
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 4240,4242 **** select unreserved_test();
--- 4240,4450 ----
  (1 row)
  
  drop function unreserved_test();
+ -- Checking a FOREACH statement
+ -- should fail
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ ERROR:  syntax error at or near "$1"
+ LINE 5:   foreach x in $1
+                        ^
+ 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:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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 requested (2) is higher than the number of array dimensions (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;
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 3375,3377 **** $$ language plpgsql;
--- 3375,3500 ----
  select unreserved_test();
  
  drop function unreserved_test();
+ 
+ -- Checking a FOREACH statement
+ -- should fail
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ 
+ 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;
#6Pavel Stehule
pavel.stehule@gmail.com
In reply to: Stephen Frost (#5)
Re: updated patch for foreach stmt

2011/2/8 Stephen Frost <sfrost@snowman.net>:

* Robert Haas (robertmhaas@gmail.com) wrote:

Amen to that!

Hopefully it helped. :)

I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr.  That's probably a good
idea, because it gives us an escape hatch against needing to invent
yet another variant of this syntax - the word immediately following IN
can be known with confidence to be intended as a keyword rather than
as part of the expression.

Alright, alright, *I* don't care that much, though I do feel it's a bit
excessive.  Updated patch against HEAD attached.

I am thinking so it is good idea. Even I have no plans to expand
plpgsql in next year :), it really opening a doors for later changes.
And it's more secure - we can check if parameter is really array or
some else.

Regards

Pavel Stehule

Show quoted text

commit a5d32fa41fbbbd9ace465f62be714366990061d4
Author: Stephen Frost <sfrost@snowman.net>
Date:   Tue Feb 8 15:57:40 2011 -0500

   PL/PgSQL FOREACH - Add ARRAY keyword

   Add ARRAY as required after IN when using FOREACH, to
   future-proof against later kinds of FOREACH commands.

       Thanks,

               Stephen

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)

iEYEARECAAYFAk1RrtUACgkQrzgMPqB3kigt6gCffjFcE4ddo76ECj+kB+iaM7DV
c2UAnRDMh1B7r+4ZrnJtIeoRUXJy42+f
=ZwQa
-----END PGP SIGNATURE-----

#7Stephen Frost
sfrost@snowman.net
In reply to: Pavel Stehule (#4)
1 attachment(s)
Re: updated patch for foreach stmt

* Pavel Stehule (pavel.stehule@gmail.com) wrote:

There is only bad keywords in doc - SCALE instead SLICE and a maybe a
usage of slicing need a example.

Err, yeah, a couple of stupid documentation issues, sorry about that.

commit 9460c0831f5de71e31823b7e9d8511d2d8124776
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 16:15:03 2011 -0500

Add ARRAY keyword to example, ewps.

commit 34a8ffd8d4cfe42bb4f698564f16bd468b9f2613
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 16:14:17 2011 -0500

Tabs are bad, mmmkay.

commit cf1ebcb7e4905cc31cd58b4fd9fa90cd488cc0c0
Author: Stephen Frost <sfrost@snowman.net>
Date: Tue Feb 8 16:13:22 2011 -0500

PL/PgSQL documentation cleanups

SCALE -> SLICE (no clue where SCALE came from..) and clarify
what a SLICE is, really.

Thanks,

Stephen

Attachments:

plpgsql_foreach_2011020803.patchtext/x-diff; charset=us-asciiDownload
*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 300,310 **** $$ LANGUAGE plpgsql;
      <para>
       All variables used in a block must be declared in the
       declarations section of the block.
!      (The only exceptions are that the loop variable of a <literal>FOR</> loop
!      iterating over a range of integer values is automatically declared as an
!      integer variable, and likewise the loop variable of a <literal>FOR</> loop
!      iterating over a cursor's result is automatically declared as a
!      record variable.)
      </para>
  
      <para>
--- 300,308 ----
      <para>
       All variables used in a block must be declared in the
       declarations section of the block.
!      (The only exceptions are the loop variables of <literal>FOR</> and
!      <literal>FOREACH</> loops which are automatically declared as the
!      appropriate variable type to match what to loop is over.)
      </para>
  
      <para>
***************
*** 1359,1375 **** GET DIAGNOSTICS integer_var = ROW_COUNT;
  
            <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).
!             <literal>FOUND</literal> is set this way when the
!             <command>FOR</> 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
!             execution of other statements within the loop body.
             </para>
            </listitem>
            <listitem>
--- 1357,1375 ----
  
            <listitem>
             <para>
!             A <command>FOR</> or <command>FOREACH</> statement sets
!             <literal>FOUND</literal> to true if it iterates one or more times,
!             else to 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) and all variants of the
!             <command>FOREACH</> statement (currently only ARRAY
!             <command>FOREACH</> loops).  <literal>FOUND</literal> is set this
!             way when the <command>FOR</> or <command>FOREACH</> loop exits;
!             inside the execution of the loop, <literal>FOUND</literal> is not
!             modified by the <command>FOR</> or <command>FOREACH</> statement,
!             although it might be changed by the execution of other statements
!             within the loop body.
             </para>
            </listitem>
            <listitem>
***************
*** 1910,1918 **** 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.
      </para>
  
      <sect3>
--- 1910,1918 ----
  
      <para>
       With the <literal>LOOP</>, <literal>EXIT</>,
!      <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,2243 **** END LOOP <optional> <replaceable>label</replaceable> </optional>;
--- 2238,2287 ----
      </para>
     </sect2>
  
+    <sect2 id="plpgsql-array-foreach-loop">
+     <title>Looping Through Arrays</title>
+ 
+     <para>
+     Similar to a <literal>FOR</> loop is the <literal>FOREACH</> loop.
+     <literal>FOREACH</> is used to loop over multi-value variables, such
+     as ARRAYs.  Other multi-value variables may be added to <literal>FOREACH</>
+     later.  Note that <literal>FOREACH</> can be thought of horizantally
+     looping, whereas <literal>FOR</> can be thought of a vertical loop.
+     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>
+ 
+     This <literal>FOREACH</> form allows looping over each variable in an
+     array, in which case the loop variable will be the array's component
+     type (eg: the single-value loop variable for an integer ARRAY would
+     be an integer), or looping over slices of the array, in which case
+     the loop variable will be an array itself of the same type as the array.
+     Note that the SLICE is one of the dimensions of the array- it's not a
+     number of elements.  Following is an example of looping through an
+     integer array:
+ 
+ <programlisting>
+ CREATE FUNCTION sum(VARIADIC int[]) RETURNS int8 AS $$
+ DECLARE
+   s int8; x int;
+ BEGIN
+   FOREACH x IN ARRAY $1
+   LOOP
+     s := s + x;
+   END LOOP;
+   RETURN s;
+ END;
+ $$ LANGUAGE plpgsql;
+ </programlisting>
+     </para>
+ 
+    </sect2>
+ 
     <sect2 id="plpgsql-error-trapping">
      <title>Trapping Errors</title>
  
*** a/src/backend/utils/adt/arrayfuncs.c
--- b/src/backend/utils/adt/arrayfuncs.c
***************
*** 68,74 **** static void CopyArrayEls(ArrayType *array,
  			 Datum *values, bool *nulls, int nitems,
  			 int typlen, bool typbyval, char typalign,
  			 bool freedata);
- static bool array_get_isnull(const bits8 *nullbitmap, int offset);
  static void array_set_isnull(bits8 *nullbitmap, int offset, bool isNull);
  static Datum ArrayCast(char *value, bool byval, int len);
  static int ArrayCastAndSet(Datum src,
--- 68,73 ----
***************
*** 3829,3834 **** arraycontained(PG_FUNCTION_ARGS)
--- 3828,3996 ----
  	PG_RETURN_BOOL(result);
  }
  
+ /*-----------------------------------------------------------------------------
+  * array iteration functions
+  *		These functions are used to efficiently iterate through arrays
+  *-----------------------------------------------------------------------------
+  */
+ 
+ /*
+  * Create an iterator for the array 'arr' which will allow us to
+  * step through the array one slice at a time (where a slice
+  * is the size of the dimension indicated by slice_ndim).
+  */
+ ArrayReader
+ array_create_iterator(ArrayType *arr, int slice_ndim)
+ {
+ 	ArrayReader reader = palloc(sizeof(ArrayReaderData));
+ 
+ 	/*
+ 	 * Store a pointer to the array itself, since we need
+ 	 * various information from it as we build up arrays
+ 	 * based off of it to return.  No need to have the
+ 	 * caller pass it in every time.
+ 	 */
+ 	reader->arr = arr;
+ 
+ 	/*
+ 	 * Initialize our data pointer to the beginning of the data
+ 	 * segment, this pointer gets moved along as we move through
+ 	 * the array.
+ 	 */
+ 	reader->data_ptr = ARR_DATA_PTR(arr);
+ 
+ 	/*
+ 	 * Similairly, initialize our current_item, this is used to
+ 	 * check against the null bitmap for each element as we walk
+ 	 * through the array.
+ 	 */
+ 	reader->current_item = 0;
+ 
+ 	/*
+ 	 * Get a pointer into the ARR_DIMS() array which starts where
+ 	 * a given slice would start at, so that we have the dimensions
+ 	 * of the slice itself when we're building an array to return.
+ 	 */
+ 	reader->slice_dims_offset = ARR_DIMS(arr) + ARR_NDIM(arr) - slice_ndim;
+ 
+ 	/*
+ 	 * Similairly, get a pointer into the ARR_LBOUND() which starts
+ 	 * where a given slice would start at.
+ 	 */
+ 	reader->slice_lbound_offset = ARR_LBOUND(arr) == 0 ?  NULL :
+ 								  ARR_LBOUND(arr) + ARR_NDIM(arr) - slice_ndim;
+ 
+ 	/*
+ 	 * Gather this information during the set-up phase, since
+ 	 * it involves a cache lookup and there's no sense doing
+ 	 * that over and over.
+ 	 */
+ 	get_typlenbyvalalign(ARR_ELEMTYPE(arr),
+ 						&reader->typlen,
+ 						&reader->typbyval,
+ 						&reader->typalign);
+ 
+ 	/* Get the total number of elements in the array */
+ 	reader->nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ 
+ 	reader->slice_len = ArrayGetNItems(slice_ndim, reader->slice_dims_offset);
+ 	reader->slice_ndim = slice_ndim;
+ 
+ 	return reader;
+ }
+ 
+ /*
+  * Iterate one slice at a time through the array which was passed to
+  * array_create_iterator based on the slice size requested, returning
+  * each slice via *value / *isnull
+  */
+ bool
+ array_iterate(ArrayReader reader, Datum *value, bool *isnull)
+ {
+ 	/* Check if we have reached the end of the array */
+ 	if (reader->current_item >= reader->nitems)
+ 		return false;
+ 
+ 	/*
+ 	 * When slice_len is zero, we are just stepping through the array
+ 	 * one element at a time.  This is the nice, simple, case.
+ 	 */
+ 	if (reader->slice_len == 0)
+ 	{
+ 		/* Return just one element. */
+ 		if (array_get_isnull(ARR_NULLBITMAP(reader->arr), reader->current_item++))
+ 		{
+ 			*isnull = true;
+ 			*value = (Datum) 0;
+ 		}
+ 		else
+ 		{
+ 			/* non-NULL, so fetch the individual Datum to return */
+ 			*isnull = false;
+ 			*value = fetch_att(reader->data_ptr, reader->typbyval,
+ 							   reader->typlen);
+ 
+ 			/* Move our data pointer forward to the next element */
+ 			reader->data_ptr = att_addlength_pointer(reader->data_ptr,
+ 													 reader->typlen,
+ 													 reader->data_ptr);
+ 			reader->data_ptr = (char *) att_align_nominal(reader->data_ptr,
+ 														  reader->typalign);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * slice_len is non-zero, so we have to build and return an array which
+ 		 * matches the slice size requested (which is the size of one of the
+ 		 * dimensions of the array we're working on).
+ 		 */
+ 
+ 		int					i;
+ 		ArrayBuildState	    *astate = NULL;
+ 
+ 		/* Returns a slice. Copy "slice_len" elements to new array. */
+ 		for (i = 0; i < reader->slice_len; i++)
+ 		{
+ 			if (array_get_isnull(ARR_NULLBITMAP(reader->arr), reader->current_item++))
+ 			{
+ 				/* Add in a NULL element, if we come across one */
+ 				astate = accumArrayResult(astate, (Datum) 0, true,
+ 										ARR_ELEMTYPE(reader->arr),
+ 										CurrentMemoryContext);
+ 			}
+ 			else
+ 			{
+ 				Datum	element;
+ 
+ 				/* Fetch each element and add it into the new array */
+ 				element = fetch_att(reader->data_ptr, reader->typbyval,
+ 									reader->typlen);
+ 
+ 				astate = accumArrayResult(astate, element, false,
+ 										ARR_ELEMTYPE(reader->arr),
+ 										CurrentMemoryContext);
+ 
+ 				/* Move our data pointer forward to the next element */
+ 				reader->data_ptr = att_addlength_pointer(reader->data_ptr,
+ 														 reader->typlen,
+ 														 reader->data_ptr);
+ 				reader->data_ptr = (char *) att_align_nominal(reader->data_ptr,
+ 															  reader->typalign);
+ 			}
+ 		}
+ 
+ 		/* Build the final array to return */
+ 		*isnull = false;
+ 		*value = makeMdArrayResult(astate, reader->slice_ndim,
+ 								   reader->slice_dims_offset,
+ 								   reader->slice_lbound_offset,
+ 								   CurrentMemoryContext, true);
+ 	}
+ 
+ 	return true;
+ }
+ 
  
  /***************************************************************************/
  /******************|		  Support  Routines			  |*****************/
***************
*** 3840,3846 **** arraycontained(PG_FUNCTION_ARGS)
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! static bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
--- 4002,4008 ----
   * nullbitmap: pointer to array's null bitmap (NULL if none)
   * offset: 0-based linear element number of array element
   */
! bool
  array_get_isnull(const bits8 *nullbitmap, int offset)
  {
  	if (nullbitmap == NULL)
*** a/src/include/utils/array.h
--- b/src/include/utils/array.h
***************
*** 115,120 **** typedef struct ArrayMapState
--- 115,148 ----
  } ArrayMapState;
  
  /*
+  * array_create_iterator() will return the below structure which is later
+  * used and updated by array_iterate() to allow the caller to efficiently
+  * step through an array one slice at a time.  A slice is defined as one
+  * dimension of the base array.
+  */
+ typedef struct
+ {
+ 	/* 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 */
+ 
+ 	/* Cache'd info about the array that is pulled during initialization */
+ 	ArrayType	   *arr;			/* array we're iterating through */
+ 	int				nitems;			/* total number of elements in array */
+ 	int16			typlen;			/* element's length */
+ 	bool			typbyval;		/* element's byval property */
+ 	char			typalign;		/* align type of element */
+ 
+ 	/* information about the requested slice */
+ 	int				slice_len;		/* number of elements per slice */
+ 	int				slice_ndim;		/* slice dimension */
+ 	int			   *slice_dims_offset; /* slice dims array */
+ 	int			   *slice_lbound_offset; /* slice lbound array */
+ } ArrayReaderData;
+ 
+ typedef ArrayReaderData *ArrayReader;
+ 
+ /*
   * fmgr macros for array objects
   */
  #define DatumGetArrayTypeP(X)		  ((ArrayType *) PG_DETOAST_DATUM(X))
***************
*** 215,220 **** extern Datum array_ref(ArrayType *array, int nSubscripts, int *indx,
--- 243,249 ----
  extern ArrayType *array_set(ArrayType *array, int nSubscripts, int *indx,
  		  Datum dataValue, bool isNull,
  		  int arraytyplen, int elmlen, bool elmbyval, char elmalign);
+ extern bool array_get_isnull(const bits8 *nullbitmap, int offset);
  extern ArrayType *array_get_slice(ArrayType *array, int nSubscripts,
  				int *upperIndx, int *lowerIndx,
  				int arraytyplen, int elmlen, bool elmbyval, char elmalign);
***************
*** 254,259 **** extern Datum makeArrayResult(ArrayBuildState *astate,
--- 283,291 ----
  extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
  				  int *dims, int *lbs, MemoryContext rcontext, bool release);
  
+ extern ArrayReader array_create_iterator(ArrayType *arr, int slice_ndim);
+ extern bool array_iterate(ArrayReader reader, Datum *value, bool *isnull);
+ 
  /*
   * prototypes for functions defined in arrayutils.c
   */
*** a/src/pl/plpgsql/src/gram.y
--- b/src/pl/plpgsql/src/gram.y
***************
*** 21,26 ****
--- 21,27 ----
  #include "parser/parse_type.h"
  #include "parser/scanner.h"
  #include "parser/scansup.h"
+ #include "utils/array.h"
  
  
  /* Location tracking support --- simpler than bison's default */
***************
*** 134,139 **** static	List			*read_raise_options(void);
--- 135,141 ----
  			PLpgSQL_datum   *scalar;
  			PLpgSQL_rec     *rec;
  			PLpgSQL_row     *row;
+ 			int	slice;
  		}						forvariable;
  		struct
  		{
***************
*** 178,184 **** static	List			*read_raise_options(void);
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
--- 180,186 ----
  %type <ival>	assign_var
  %type <var>		cursor_variable
  %type <datum>	decl_cursor_arg
! %type <forvariable>	for_variable foreach_control
  %type <stmt>	for_control
  
  %type <str>		any_identifier opt_block_label opt_label
***************
*** 190,196 **** 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 <list>	proc_exceptions
  %type <exception_block> exception_sect
--- 192,198 ----
  %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 stmt_foreach_a
  
  %type <list>	proc_exceptions
  %type <exception_block> exception_sect
***************
*** 239,244 **** static	List			*read_raise_options(void);
--- 241,247 ----
  %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,269 **** static	List			*read_raise_options(void);
--- 267,273 ----
  %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,303 **** static	List			*read_raise_options(void);
--- 302,308 ----
  %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,744 **** proc_stmt		: pl_block ';'
--- 744,751 ----
  						{ $$ = $1; }
  				| stmt_for
  						{ $$ = $1; }
+ 				| stmt_foreach_a
+ 						{ $$ = $1; }
  				| stmt_exit
  						{ $$ = $1; }
  				| stmt_return
***************
*** 1386,1391 **** for_variable	: T_DATUM
--- 1393,1456 ----
  					}
  				;
  
+ stmt_foreach_a		: opt_block_label K_FOREACH foreach_control K_IN K_ARRAY expr_until_loop loop_body
+ 					{
+ 						PLpgSQL_stmt_foreach_a *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->expr = $6;
+ 						new->slice = $3.slice;
+ 						new->body = $7.stmts;
+ 
+ 						if ($3.rec)
+ 						{
+ 							new->rec = $3.rec;
+ 							check_assignable((PLpgSQL_datum *) new->rec, @3);
+ 						}
+ 						else if ($3.row)
+ 						{
+ 							new->row = $3.row;
+ 							check_assignable((PLpgSQL_datum *) new->row, @3);
+ 						}
+ 						else if ($3.scalar)
+ 						{
+ 							Assert($3.scalar->dtype == PLPGSQL_DTYPE_VAR);
+ 							new->var = (PLpgSQL_var *) $3.scalar;
+ 							check_assignable($3.scalar, @3);
+ 
+ 						}
+ 						else
+ 						{
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_SYNTAX_ERROR),
+ 									 errmsg("loop variable of loop over array must be a record, row variable, scalar variable or list of scalar variables"),
+ 											 parser_errposition(@3)));
+ 						}
+ 
+ 						check_labels($1, $7.end_label, $7.end_label_location);
+ 						$$ = (PLpgSQL_stmt *) new;
+ 					}
+ 				;
+ 
+ foreach_control		: for_variable
+ 					{
+ 						$$ = $1;
+ 						$$.slice = 0;
+ 					}
+ 				| for_variable K_SLICE ICONST
+ 					{
+ 						$$ = $1;
+ 						$$.slice = $3;
+ 						if ($3 < 0 || $3 >= MAXDIM)
+ 							ereport(ERROR,
+ 									(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ 									 errmsg("number of slicing array dimensions (%d) exceeds the maximum allowed (%d)",
+ 												$3, MAXDIM),
+ 											 parser_errposition(@3)));
+ 					}
+ 				;
+ 
  stmt_exit		: exit_type opt_label opt_exitcond
  					{
  						PLpgSQL_stmt_exit *new;
***************
*** 2035,2040 **** any_identifier	: T_WORD
--- 2100,2106 ----
  unreserved_keyword	:
  				K_ABSOLUTE
  				| K_ALIAS
+ 				| K_ARRAY
  				| K_BACKWARD
  				| K_CONSTANT
  				| K_CURSOR
***************
*** 2063,2068 **** unreserved_keyword	:
--- 2129,2135 ----
  				| K_ROW_COUNT
  				| K_ROWTYPE
  				| K_SCROLL
+ 				| K_SLICE
  				| K_SQLSTATE
  				| K_TYPE
  				| K_USE_COLUMN
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 107,112 **** static int exec_stmt_fors(PLpgSQL_execstate *estate,
--- 107,114 ----
  			   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,1317 **** exec_stmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt)
--- 1314,1323 ----
  			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;
***************
*** 2026,2031 **** exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
--- 2032,2231 ----
  	return rc;
  }
  
+ /* ----------
+  * exec_stmt_foreach_a			Implements loop over array
+  *
+  * This allows iterating over an array using either a single-step
+  * approach, or by doing it in slices where a slice is one dimension
+  * of the array.  When single-step'ing, the loop variable is the same
+  * type that the array stores (eg: integer), when looping through
+  * slices, the loop variable is, itself, 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)
+ {
+ 	/* variable which will be changing as we loop over the array */
+ 	Datum				value;
+ 	bool				isnull;
+ 
+ 	ArrayType		   *arr;					/* Our copy of the array */
+ 	Oid					valtype;				/* array value type */
+ 	Oid					elmOid;
+ 
+ 	/* control variable */
+ 	PLpgSQL_datum	   *ctrl_var = NULL;		/* make compiler quiet */
+ 	Oid					ctrl_var_type = InvalidOid;
+ 
+ 	/* return variables */
+ 	bool				found = false;
+ 	int					rc = PLPGSQL_RC_OK;
+ 
+ 	/* Our iterator for looping over the array */
+ 	ArrayReader			array_iterator;
+ 
+ 	/* get the value of the array expression using array_expr */
+ 	value = exec_eval_expr(estate, stmt->expr, &isnull, &valtype);
+ 	if (isnull)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ 				 errmsg("NULL value isn't allowed as parameter of FOREACH-IN")));
+ 
+ 	/* check the type of the expression - must be an array */
+ 	if (!OidIsValid(get_element_type(valtype)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("result of expression isn't an array"),
+ 				 errdetail("result of expression is %s",
+ 									format_type_be(valtype))));
+ 
+ 	/* make a copy of the array, is this necessary? */
+ 	arr = DatumGetArrayTypePCopy(value);
+ 
+ 	/* Clean up any leftover temporary memory */
+ 	exec_eval_cleanup(estate);
+ 
+ 	/*
+ 	 * If the target needs to be an array, check that it actually is,
+ 	 * and that it has a valid dimension for the array we're looping
+ 	 * through.
+ 	 */
+ 
+ 	/* Attempt to get the element OID, for error checking below */
+ 	elmOid = InvalidOid;
+ 	if (stmt->var != NULL)
+ 		elmOid = get_element_type(stmt->var->datatype->typoid);
+ 
+ 	/* slice dimension should be less or equal to array dimension */
+ 	if (stmt->slice > ARR_NDIM(arr))
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("slice dimension requested (%d) is higher than the number of array dimensions (%d)",
+ 								    stmt->slice, ARR_NDIM(arr))));
+ 
+ 	/* Trying to do a slice, but we weren't given an array variable */
+ 	if (stmt->slice > 0 && elmOid == InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("target variable \"%s\" for sliced array should be an array type",
+ 							stmt->var ? stmt->var->refname :
+ 							stmt->row ? stmt->row->refname :
+ 							stmt->rec ? stmt->rec->refname : "Unknown"),
+ 				 errhint("Value assigned will be of an array type.")));
+ 
+ 	/* Trying to loop through as a scalar but given an array variable */
+ 	if (stmt->slice == 0 && stmt->var != NULL && elmOid != InvalidOid)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 				 errmsg("target variable \"%s\" is  an array",
+ 							stmt->var->refname),
+ 				errhint("Value assigned will be of a scalar type")));
+ 
+ 	/* Set up the control variable/variable type */
+ 	if (stmt->slice > 0)
+ 	{
+ 		/* Doing array-based slices, set up our control variable */
+ 		ctrl_var_type = valtype;
+ 		ctrl_var = estate->datums[stmt->var->dno];
+ 	}
+ 	else
+ 	{
+ 		/* Looping one element at a time */
+ 
+ 		/* without slicing, loop var is the same type as an array element */
+ 		ctrl_var_type = ARR_ELEMTYPE(arr);
+ 
+ 		/* Figure out what kind of variable the element is going to be */
+ 		if (stmt->var != NULL)
+ 			ctrl_var = estate->datums[stmt->var->dno];
+ 		else if (stmt->row != NULL)
+ 			ctrl_var = estate->datums[stmt->row->dno];
+ 		else
+ 			ctrl_var = estate->datums[stmt->rec->dno];
+ 	}
+ 
+ 	/* Create an iterator to step through the array, a slice at a time */
+ 	array_iterator = array_create_iterator(arr, stmt->slice);
+ 
+ 	/*
+ 	 * Iterate over the array, a slice at a time, and execute the statements
+ 	 * in the block with the variable set to the current element or slice
+ 	 */
+ 	while (array_iterate(array_iterator, &value, &isnull))
+ 	{
+ 		/* There is minimally one loop, so assign "found" to true */
+ 		found = true;
+ 
+ 		/* Fill in the control variable */
+ 		exec_assign_value(estate, ctrl_var, value, ctrl_var_type, &isnull);
+ 
+ 		/*
+ 		 * 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;
+ 			}
+ 		}
+ 	}
+ 
+ 	pfree(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
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 230,235 **** plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
--- 230,237 ----
  			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:
***************
*** 293,298 **** static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
--- 295,301 ----
  static void dump_close(PLpgSQL_stmt_close *stmt);
  static void dump_perform(PLpgSQL_stmt_perform *stmt);
  static void dump_expr(PLpgSQL_expr *expr);
+ static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
  
  
  static void
***************
*** 376,381 **** dump_stmt(PLpgSQL_stmt *stmt)
--- 379,387 ----
  		case PLPGSQL_STMT_PERFORM:
  			dump_perform((PLpgSQL_stmt_perform *) stmt);
  			break;
+ 		case PLPGSQL_STMT_FOREACH_A:
+ 			dump_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ 			break;
  		default:
  			elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
  			break;
***************
*** 596,601 **** dump_forc(PLpgSQL_stmt_forc *stmt)
--- 602,625 ----
  }
  
  static void
+ dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+ {
+ 	dump_ind();
+ 	printf("FOREACHA %s", (stmt->rec != NULL) ? stmt->rec->refname :
+ 						    (stmt->row != NULL) ? stmt->row->refname : stmt->var->refname);
+ 	if (stmt->slice != 0)
+ 		printf("SLICE %d ", stmt->slice);
+ 	printf("IN ");
+ 	dump_expr(stmt->expr);
+ 	printf("\n");
+ 
+ 	dump_stmts(stmt->body);
+ 
+ 	dump_ind();
+ 	printf("    ENDOFOREACHA");
+ }
+ 
+ static void
  dump_open(PLpgSQL_stmt_open *stmt)
  {
  	dump_ind();
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 77,82 **** static const ScanKeyword reserved_keywords[] = {
--- 77,83 ----
  	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,110 **** static const int num_reserved_keywords = lengthof(reserved_keywords);
--- 106,112 ----
  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,138 **** static const ScanKeyword unreserved_keywords[] = {
--- 135,141 ----
  	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)
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 87,92 **** enum PLpgSQL_stmt_types
--- 87,93 ----
  	PLPGSQL_STMT_CASE,
  	PLPGSQL_STMT_LOOP,
  	PLPGSQL_STMT_WHILE,
+ 	PLPGSQL_STMT_FOREACH_A,
  	PLPGSQL_STMT_FORI,
  	PLPGSQL_STMT_FORS,
  	PLPGSQL_STMT_FORC,
***************
*** 427,432 **** typedef struct
--- 428,447 ----
  
  
  typedef struct
+ {								/* FOREACH item in array loop */
+ 	int			cmd_type;
+ 	int			lineno;
+ 	char	   *label;
+ 	PLpgSQL_var *var;
+ 	PLpgSQL_rec *rec;
+ 	PLpgSQL_row *row;
+ 	PLpgSQL_expr	*expr;
+ 	int	   slice;
+ 	List	   *body;			/* List of statements */
+ } PLpgSQL_stmt_foreach_a;
+ 
+ 
+ typedef struct
  {								/* FOR statement with integer loopvar	*/
  	int			cmd_type;
  	int			lineno;
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 4240,4242 **** select unreserved_test();
--- 4240,4450 ----
  (1 row)
  
  drop function unreserved_test();
+ -- Checking a FOREACH statement
+ -- should fail
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ ERROR:  syntax error at or near "$1"
+ LINE 5:   foreach x in $1
+                        ^
+ 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:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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:  target variable "x" for sliced array should be an array type
+ HINT:  Value assigned will 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 requested (2) is higher than the number of array dimensions (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;
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 3375,3377 **** $$ language plpgsql;
--- 3375,3500 ----
  select unreserved_test();
  
  drop function unreserved_test();
+ 
+ -- Checking a FOREACH statement
+ -- should fail
+ create function foreach_test(anyarray)
+ returns void as $$
+ declare x int;
+ begin
+   foreach x in $1
+   loop
+     raise notice '%', x;
+   end loop;
+   end;
+ $$ language plpgsql;
+ 
+ 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;
#8Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#3)
Re: updated patch for foreach stmt

* Robert Haas (robertmhaas@gmail.com) wrote:

I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr. That's probably a good
idea, because it gives us an escape hatch against needing to invent
yet another variant of this syntax - the word immediately following IN
can be known with confidence to be intended as a keyword rather than
as part of the expression.

Alright, so, for lack of anything better to do, I went ahead and marked
it Ready for Committer. If that was wrong or someone wants to do
another review, etc, let me know..

Thanks,

Stephen

#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#3)
Re: updated patch for foreach stmt

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Feb 8, 2011 at 3:26 PM, Stephen Frost <sfrost@snowman.net> wrote:

Alright, so, like I said, I really like this feature and would like to
see it included.

Amen to that!

I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr.

Actually, I'm on record as saying the opposite: we shouldn't need to
distinguish the exact data type at the syntax level, so long as the
FOREACH construct is understood to mean "iterate through the members of
the composite object produced by this expression":

http://archives.postgresql.org/pgsql-hackers/2010-12/msg01579.php

I am not, however, wedded to that position --- if people are happier
with explicit use of ARRAY here, I won't fight hard to get rid of it.

Anyway I'm going to start on this patch next, so last chance for
opinions about the syntax ...

regards, tom lane

#10Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#9)
Re: updated patch for foreach stmt

On Tue, Feb 15, 2011 at 8:44 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Robert Haas <robertmhaas@gmail.com> writes:

On Tue, Feb 8, 2011 at 3:26 PM, Stephen Frost <sfrost@snowman.net> wrote:

Alright, so, like I said, I really like this feature and would like to
see it included.

Amen to that!

I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr.

Actually, I'm on record as saying the opposite: we shouldn't need to
distinguish the exact data type at the syntax level, so long as the
FOREACH construct is understood to mean "iterate through the members of
the composite object produced by this expression":

http://archives.postgresql.org/pgsql-hackers/2010-12/msg01579.php

I am not, however, wedded to that position --- if people are happier
with explicit use of ARRAY here, I won't fight hard to get rid of it.

Anyway I'm going to start on this patch next, so last chance for
opinions about the syntax ...

Oh, I was looking at this one:

http://archives.postgresql.org/pgsql-hackers/2010-12/msg01557.php

Anyhoo, forcing the explicit ARRAY keyword in there seems like pretty
cheap future-proofing to me. YMMV.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#11Stephen Frost
sfrost@snowman.net
In reply to: Robert Haas (#10)
Re: updated patch for foreach stmt

* Robert Haas (robertmhaas@gmail.com) wrote:

On Tue, Feb 15, 2011 at 8:44 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Anyway I'm going to start on this patch next, so last chance for
opinions about the syntax ...

Oh, I was looking at this one:

http://archives.postgresql.org/pgsql-hackers/2010-12/msg01557.php

Anyhoo, forcing the explicit ARRAY keyword in there seems like pretty
cheap future-proofing to me. YMMV.

+1 for this, I don't see it as a big deal, and I would hate to discover
there's some reason we care (I dunno, implicit casts from ARRAY to
hstore ?) in the future that we're not thinking about now.

This also means there's no ambiguity as to what the iterator variable
should be declared as- if you're doing a FOREACH .. ARRAY, then your
iterator is an ARRAY (if it's not a scalar, of course), full stop.

Thanks,

Stephen

#12Andrew Dunstan
andrew@dunslane.net
In reply to: Robert Haas (#10)
Re: updated patch for foreach stmt

On 02/15/2011 08:59 PM, Robert Haas wrote:

On Tue, Feb 15, 2011 at 8:44 PM, Tom Lane<tgl@sss.pgh.pa.us> wrote:

Robert Haas<robertmhaas@gmail.com> writes:

On Tue, Feb 8, 2011 at 3:26 PM, Stephen Frost<sfrost@snowman.net> wrote:

Alright, so, like I said, I really like this feature and would like to
see it included.

Amen to that!
I think the syntax Tom suggested before was FOREACH thingy IN ARRAY
arr rather than just FOREACH thingy IN arr.

Actually, I'm on record as saying the opposite: we shouldn't need to
distinguish the exact data type at the syntax level, so long as the
FOREACH construct is understood to mean "iterate through the members of
the composite object produced by this expression":

http://archives.postgresql.org/pgsql-hackers/2010-12/msg01579.php

I am not, however, wedded to that position --- if people are happier
with explicit use of ARRAY here, I won't fight hard to get rid of it.

Anyway I'm going to start on this patch next, so last chance for
opinions about the syntax ...

Oh, I was looking at this one:

http://archives.postgresql.org/pgsql-hackers/2010-12/msg01557.php

Anyhoo, forcing the explicit ARRAY keyword in there seems like pretty
cheap future-proofing to me. YMMV.

If this is the syntax that makes you do things like:

FOREACH foo IN ARRAY ARRAY[1,2,3]

I have to say I find that pretty darn ugly still.

cheers

andrew

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#12)
Re: updated patch for foreach stmt

Andrew Dunstan <andrew@dunslane.net> writes:

On 02/15/2011 08:59 PM, Robert Haas wrote:

Anyhoo, forcing the explicit ARRAY keyword in there seems like pretty
cheap future-proofing to me. YMMV.

If this is the syntax that makes you do things like:
FOREACH foo IN ARRAY ARRAY[1,2,3]
I have to say I find that pretty darn ugly still.

Yeah, that was the argument against requiring ARRAY. So it comes down
to whether you think we need future-proofing here. I can't immediately
see any reason for us to need a keyword right there, but ...

regards, tom lane

#14Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#13)
Re: updated patch for foreach stmt

2011/2/16 Tom Lane <tgl@sss.pgh.pa.us>:

Andrew Dunstan <andrew@dunslane.net> writes:

On 02/15/2011 08:59 PM, Robert Haas wrote:

Anyhoo, forcing the explicit ARRAY keyword in there seems like pretty
cheap future-proofing to me.  YMMV.

If this is the syntax that makes you do things like:
     FOREACH foo IN ARRAY ARRAY[1,2,3]
I have to say I find that pretty darn ugly still.

Yeah, that was the argument against requiring ARRAY.  So it comes down
to whether you think we need future-proofing here.  I can't immediately
see any reason for us to need a keyword right there, but ...

the combination of two keywords isn't nice, but we can ensure so
result of expression will has a requested type. It's more verbose,
it's more secure. We can to check a allowed keywords like SCALING in
compile time, we can use a more keywords - A hash type can need a
separation between KEY and VALUE - so any keyword there enables a
higher possibilities in future. We can do it without a auxiliary
keyword too, but parser will be more complex.

Regards

Pavel Stehule

Show quoted text

                       regards, tom lane

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Stephen Frost (#7)
Re: updated patch for foreach stmt

Stephen Frost <sfrost@snowman.net> writes:

* Pavel Stehule (pavel.stehule@gmail.com) wrote:

There is only bad keywords in doc - SCALE instead SLICE and a maybe a
usage of slicing need a example.

Err, yeah, a couple of stupid documentation issues, sorry about that.

Applied with assorted cleanup. I left the syntax as-is, since that
seems to be the plurality position at the moment.

regards, tom lane