From ebcb4c931dc0ea5bc5e2199f39996f99fcab842b Mon Sep 17 00:00:00 2001
From: Neil Conway <neilc@samurai.com>
Date: Wed, 22 Jun 2005 01:35:03 +0000
Subject: [PATCH] Add a CONTINUE statement to PL/PgSQL, which can be used to
 begin the next iteration of a loop. Update documentation and add regression
 tests. Patch from Pavel Stehule, reviewed by Neil Conway.

---
 doc/src/sgml/plpgsql.sgml             |  94 +++++++++---
 src/pl/plpgsql/src/gram.y             |  33 +++--
 src/pl/plpgsql/src/pl_exec.c          | 204 ++++++++++++++++++++------
 src/pl/plpgsql/src/pl_funcs.c         |   5 +-
 src/pl/plpgsql/src/plpgsql.h          |  11 +-
 src/pl/plpgsql/src/scan.l             |   3 +-
 src/test/regress/expected/plpgsql.out | 137 +++++++++++++++++
 src/test/regress/sql/plpgsql.sql      |  94 ++++++++++++
 8 files changed, 501 insertions(+), 80 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index b202bba7b44..e8d687928f8 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.73 2005/06/19 23:39:05 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.74 2005/06/22 01:35:02 neilc Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -1779,10 +1779,10 @@ END IF;
     </indexterm>
 
     <para>
-     With the <literal>LOOP</>, <literal>EXIT</>, <literal>WHILE</>,
-     and <literal>FOR</> statements, you can arrange for your
-     <application>PL/pgSQL</application> function to repeat a series
-     of commands.
+     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>
@@ -1807,30 +1807,36 @@ END LOOP;
      <sect3>
       <title><literal>EXIT</></title>
 
+     <indexterm>
+      <primary>EXIT</primary>
+      <secondary>in PL/pgSQL</secondary>
+     </indexterm>
+
 <synopsis>
 EXIT <optional> <replaceable>label</replaceable> </optional> <optional> WHEN <replaceable>expression</replaceable> </optional>;
 </synopsis>
 
        <para>
-        If no <replaceable>label</replaceable> is given,
-        the innermost loop is terminated and the
-        statement following <literal>END LOOP</> is executed next.
-        If <replaceable>label</replaceable> is given, it
-        must be the label of the current or some outer level of nested loop
-        or block. Then the named loop or block is terminated and control
-        continues with the statement after the loop's/block's corresponding
-        <literal>END</>.
+        If no <replaceable>label</replaceable> is given, the innermost
+        loop is terminated and the statement following <literal>END
+        LOOP</> is executed next.  If <replaceable>label</replaceable>
+        is given, it must be the label of the current or some outer
+        level of nested loop or block. Then the named loop or block is
+        terminated and control continues with the statement after the
+        loop's/block's corresponding <literal>END</>.
        </para>
 
        <para>
-        If <literal>WHEN</> is present, loop exit occurs only if the specified
-        condition is true, otherwise control passes to the statement after
-        <literal>EXIT</>.
+        If <literal>WHEN</> is specified, the loop exit occurs only if
+        <replaceable>expression</> is true. Otherwise, control passes
+        to the statement after <literal>EXIT</>.
        </para>
 
        <para>
-        <literal>EXIT</> can be used to cause early exit from all types of
-        loops; it is not limited to use with unconditional loops.
+        <literal>EXIT</> can be used with all types of loops; it is
+        not limited to use with unconditional loops. When used with a
+        <literal>BEGIN</literal> block, <literal>EXIT</literal> passes
+        control to the next statement after the end of the block.
        </para>
 
        <para>
@@ -1858,9 +1864,61 @@ END;
        </para>
      </sect3>
 
+     <sect3>
+      <title><literal>CONTINUE</></title>
+
+     <indexterm>
+      <primary>CONTINUE</primary>
+      <secondary>in PL/pgSQL</secondary>
+     </indexterm>
+
+<synopsis>
+CONTINUE <optional> <replaceable>label</replaceable> </optional> <optional> WHEN <replaceable>expression</replaceable> </optional>;
+</synopsis>
+
+       <para>
+        If no <replaceable>label</> is given, the next iteration of
+        the innermost loop is begun. That is, control is passed back
+        to the loop control expression (if any), and the body of the
+        loop is re-evaluated. If <replaceable>label</> is present, it
+        specifies the label of the loop whose execution will be
+        continued.
+       </para>
+
+       <para>
+        If <literal>WHEN</> is specified, the next iteration of the
+        loop is begun only if <replaceable>expression</> is
+        true. Otherwise, control passes to the statement after
+        <literal>CONTINUE</>.
+       </para>
+
+       <para>
+        <literal>CONTINUE</> can be used with all types of loops; it
+        is not limited to use with unconditional loops.
+       </para>
+
+       <para>
+        Examples:
+<programlisting>
+LOOP
+    -- some computations
+    EXIT WHEN count &gt; 100;
+    CONTINUE WHEN count &lt; 50;
+    -- some computations for count IN [50 .. 100] 
+END LOOP;
+</programlisting>
+       </para>
+     </sect3>
+
+
      <sect3>
       <title><literal>WHILE</></title>
 
+     <indexterm>
+      <primary>WHILE</primary>
+      <secondary>in PL/pgSQL</secondary>
+     </indexterm>
+
 <synopsis>
 <optional>&lt;&lt;<replaceable>label</replaceable>&gt;&gt;</optional>
 WHILE <replaceable>expression</replaceable> LOOP
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index e8350686eee..f33d373883e 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -4,7 +4,7 @@
  *						  procedural language
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.76 2005/06/14 06:43:14 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.77 2005/06/22 01:35:02 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -61,6 +61,7 @@ static	void			 plpgsql_sql_error_callback(void *arg);
 
 %union {
 		int32					ival;
+		bool					boolean;
 		char					*str;
 		struct
 		{
@@ -100,7 +101,7 @@ static	void			 plpgsql_sql_error_callback(void *arg);
 %type <declhdr> decl_sect
 %type <varname> decl_varname
 %type <str>		decl_renname
-%type <ival>	decl_const decl_notnull
+%type <boolean>	decl_const decl_notnull exit_type
 %type <expr>	decl_defval decl_cursor_query
 %type <dtype>	decl_datatype
 %type <row>		decl_cursor_args
@@ -153,6 +154,7 @@ static	void			 plpgsql_sql_error_callback(void *arg);
 %token	K_BEGIN
 %token	K_CLOSE
 %token	K_CONSTANT
+%token	K_CONTINUE
 %token	K_CURSOR
 %token	K_DEBUG
 %token	K_DECLARE
@@ -514,9 +516,9 @@ decl_renname	: T_WORD
 				;
 
 decl_const		:
-					{ $$ = 0; }
+					{ $$ = false; }
 				| K_CONSTANT
-					{ $$ = 1; }
+					{ $$ = true; }
 				;
 
 decl_datatype	:
@@ -531,9 +533,9 @@ decl_datatype	:
 				;
 
 decl_notnull	:
-					{ $$ = 0; }
+					{ $$ = false; }
 				| K_NOT K_NULL
-					{ $$ = 1; }
+					{ $$ = true; }
 				;
 
 decl_defval		: ';'
@@ -1035,13 +1037,14 @@ stmt_select		: K_SELECT lno
 					}
 				;
 
-stmt_exit		: K_EXIT lno opt_exitlabel opt_exitcond
+stmt_exit		: exit_type lno opt_exitlabel opt_exitcond
 					{
 						PLpgSQL_stmt_exit *new;
 
 						new = palloc0(sizeof(PLpgSQL_stmt_exit));
 						new->cmd_type = PLPGSQL_STMT_EXIT;
-						new->lineno   = $2;
+						new->is_exit  = $1;
+						new->lineno	  = $2;
 						new->label	  = $3;
 						new->cond	  = $4;
 
@@ -1049,6 +1052,16 @@ stmt_exit		: K_EXIT lno opt_exitlabel opt_exitcond
 					}
 				;
 
+exit_type		: K_EXIT
+					{
+						$$ = true;
+					}
+				| K_CONTINUE
+					{
+						$$ = false;
+					}
+				;
+
 stmt_return		: K_RETURN lno
 					{
 						PLpgSQL_stmt_return *new;
@@ -1056,8 +1069,8 @@ stmt_return		: K_RETURN lno
 						new = palloc0(sizeof(PLpgSQL_stmt_return));
 						new->cmd_type = PLPGSQL_STMT_RETURN;
 						new->lineno   = $2;
-						new->expr = NULL;
-						new->retvarno	= -1;
+						new->expr	  = NULL;
+						new->retvarno = -1;
 
 						if (plpgsql_curr_compile->fn_retset)
 						{
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 04403fa5640..d1ea2d843ee 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.146 2005/06/20 22:51:29 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.147 2005/06/22 01:35:02 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -194,6 +194,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
 	PLpgSQL_execstate estate;
 	ErrorContextCallback plerrcontext;
 	int			i;
+	int			rc;
 
 	/*
 	 * Setup the execution state
@@ -282,13 +283,24 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
 	 */
 	estate.err_text = NULL;
 	estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-	if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
+	rc = exec_stmt_block(&estate, func->action);
+	if (rc != PLPGSQL_RC_RETURN)
 	{
 		estate.err_stmt = NULL;
 		estate.err_text = NULL;
-		ereport(ERROR,
-		   (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-			errmsg("control reached end of function without RETURN")));
+
+		/*
+		 * Provide a more helpful message if a CONTINUE has been used
+		 * outside a loop.
+		 */
+		if (rc == PLPGSQL_RC_CONTINUE)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("CONTINUE cannot be used outside a loop")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+					 errmsg("control reached end of function without RETURN")));
 	}
 
 	/*
@@ -393,6 +405,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 	PLpgSQL_execstate estate;
 	ErrorContextCallback plerrcontext;
 	int			i;
+	int			rc;
 	PLpgSQL_var *var;
 	PLpgSQL_rec *rec_new,
 			   *rec_old;
@@ -546,13 +559,24 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 	 */
 	estate.err_text = NULL;
 	estate.err_stmt = (PLpgSQL_stmt *) (func->action);
-	if (exec_stmt_block(&estate, func->action) != PLPGSQL_RC_RETURN)
+	rc = exec_stmt_block(&estate, func->action);
+	if (rc != PLPGSQL_RC_RETURN)
 	{
 		estate.err_stmt = NULL;
 		estate.err_text = NULL;
-		ereport(ERROR,
-		   (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
-			errmsg("control reached end of trigger procedure without RETURN")));
+
+		/*
+		 * Provide a more helpful message if a CONTINUE has been used
+		 * outside a loop.
+		 */
+		if (rc == PLPGSQL_RC_CONTINUE)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("CONTINUE cannot be used outside a loop")));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+					 errmsg("control reached end of trigger procedure without RETURN")));
 	}
 
 	if (estate.retisset)
@@ -919,7 +943,9 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 	switch (rc)
 	{
 		case PLPGSQL_RC_OK:
-			return PLPGSQL_RC_OK;
+		case PLPGSQL_RC_CONTINUE:
+		case PLPGSQL_RC_RETURN:
+			return rc;
 
 		case PLPGSQL_RC_EXIT:
 			if (estate->exitlabel == NULL)
@@ -930,10 +956,7 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 				return PLPGSQL_RC_EXIT;
 			estate->exitlabel = NULL;
 			return PLPGSQL_RC_OK;
-
-		case PLPGSQL_RC_RETURN:
-			return PLPGSQL_RC_RETURN;
-
+		
 		default:
 			elog(ERROR, "unrecognized rc: %d", rc);
 	}
@@ -1120,7 +1143,7 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
 	{
 		PLpgSQL_diag_item	*diag_item = (PLpgSQL_diag_item *) lfirst(lc);
 		PLpgSQL_datum		*var;
-		bool				 isnull = false;
+		bool				 isnull;
 
 		if (diag_item->target <= 0)
 			continue;
@@ -1165,7 +1188,7 @@ static int
 exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
 {
 	bool		value;
-	bool		isnull = false;
+	bool		isnull;
 
 	value = exec_eval_boolean(estate, stmt->cond, &isnull);
 	exec_eval_cleanup(estate);
@@ -1209,10 +1232,23 @@ exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
 					return PLPGSQL_RC_OK;
 				if (stmt->label == NULL)
 					return PLPGSQL_RC_EXIT;
-				if (strcmp(stmt->label, estate->exitlabel))
+				if (strcmp(stmt->label, estate->exitlabel) != 0)
 					return PLPGSQL_RC_EXIT;
 				estate->exitlabel = NULL;
 				return PLPGSQL_RC_OK;
+				
+			case PLPGSQL_RC_CONTINUE:
+				if (estate->exitlabel == NULL)
+					/* anonymous continue, so re-run the loop */
+					break;
+				else if (stmt->label != NULL &&
+						 strcmp(stmt->label, estate->exitlabel) == 0)
+					/* label matches named continue, so re-run loop */
+					estate->exitlabel = NULL;
+				else
+					/* label doesn't match named continue, so propagate upward */
+					return PLPGSQL_RC_CONTINUE;
+				break;
 
 			case PLPGSQL_RC_RETURN:
 				return PLPGSQL_RC_RETURN;
@@ -1236,7 +1272,7 @@ static int
 exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
 {
 	bool		value;
-	bool		isnull = false;
+	bool		isnull;
 	int			rc;
 
 	for (;;)
@@ -1264,6 +1300,19 @@ exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
 				estate->exitlabel = NULL;
 				return PLPGSQL_RC_OK;
 
+			case PLPGSQL_RC_CONTINUE:
+				if (estate->exitlabel == NULL)
+					/* anonymous continue, so re-run loop */
+					break;
+				else if (stmt->label != NULL &&
+						 strcmp(stmt->label, estate->exitlabel) == 0)
+					/* label matches named continue, so re-run loop */
+					estate->exitlabel = NULL;
+				else
+					/* label doesn't match named continue, propagate upward */
+					return PLPGSQL_RC_CONTINUE;
+				break;
+
 			case PLPGSQL_RC_RETURN:
 				return PLPGSQL_RC_RETURN;
 
@@ -1288,7 +1337,7 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
 	PLpgSQL_var *var;
 	Datum		value;
 	Oid			valtype;
-	bool		isnull = false;
+	bool		isnull;
 	bool		found = false;
 	int			rc = PLPGSQL_RC_OK;
 
@@ -1366,13 +1415,35 @@ exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
 			}
 
 			/*
-			 * otherwise, we processed a labelled exit that does not match
-			 * the current statement's label, if any: return RC_EXIT so
-			 * that the EXIT continues to recurse upward.
+			 * 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)
+				/* anonymous continue, so continue the current loop */
+				;
+			else if (stmt->label != NULL &&
+					 strcmp(stmt->label, estate->exitlabel) == 0)
+			{
+				/* labelled continue, matches the current stmt's label */
+				estate->exitlabel = NULL;
+			}
+			else
+			{
+			    /*
+				 * otherwise, this is a labelled 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;
+			}
+		}
 
 		/*
 		 * Increase/decrease loop var
@@ -1459,17 +1530,8 @@ exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
 			 * Execute the statements
 			 */
 			rc = exec_stmts(estate, stmt->body);
-
 			if (rc != PLPGSQL_RC_OK)
 			{
-				/*
-				 * We're aborting the loop, so cleanup and set FOUND.
-				 * (This code should match the code after the loop.)
-				 */
-				SPI_freetuptable(tuptab);
-				SPI_cursor_close(portal);
-				exec_set_found(estate, found);
-
 				if (rc == PLPGSQL_RC_EXIT)
 				{
 					if (estate->exitlabel == NULL)
@@ -1490,6 +1552,34 @@ exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
 					 * recurse upward.
 					 */
 				}
+				else if (rc == PLPGSQL_RC_CONTINUE)
+				{
+					if (estate->exitlabel == NULL)
+						/* unlabelled continue, continue the current loop */
+						continue;
+					else if (stmt->label != NULL &&
+							 strcmp(stmt->label, estate->exitlabel) == 0)
+					{
+						/* labelled continue, matches the current stmt's label */
+						estate->exitlabel = NULL;
+						continue;
+					}
+
+					/*
+					 * otherwise, we processed a labelled continue
+					 * that does not match the current statement's
+					 * label, if any: return RC_CONTINUE so that the
+					 * CONTINUE will propagate up the stack.
+					 */
+				}
+
+				/*
+				 * We're aborting the loop, so cleanup and set FOUND.
+				 * (This code should match the code after the loop.)
+				 */
+				SPI_freetuptable(tuptab);
+				SPI_cursor_close(portal);
+				exec_set_found(estate, found);
 
 				return rc;
 			}
@@ -1563,7 +1653,7 @@ exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
 	n = estate->eval_processed;
 
 	/*
-	 * If the query didn't return any row, set the target to NULL and
+	 * If the query didn't return any rows, set the target to NULL and
 	 * return.
 	 */
 	if (n == 0)
@@ -1586,28 +1676,33 @@ exec_stmt_select(PLpgSQL_execstate *estate, PLpgSQL_stmt_select *stmt)
 
 
 /* ----------
- * exec_stmt_exit			Start exiting loop(s) or blocks
+ * exec_stmt_exit			Implements EXIT and CONTINUE
+ *
+ * This begins the process of exiting / restarting a loop.
  * ----------
  */
 static int
 exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
 {
 	/*
-	 * If the exit has a condition, check that it's true
+	 * If the exit / continue has a condition, evaluate it
 	 */
 	if (stmt->cond != NULL)
 	{
 		bool		value;
-		bool		isnull = false;
+		bool		isnull;
 
 		value = exec_eval_boolean(estate, stmt->cond, &isnull);
 		exec_eval_cleanup(estate);
-		if (isnull || !value)
+		if (isnull || value == false)
 			return PLPGSQL_RC_OK;
 	}
 
 	estate->exitlabel = stmt->label;
-	return PLPGSQL_RC_EXIT;
+	if (stmt->is_exit)
+		return PLPGSQL_RC_EXIT;
+	else
+		return PLPGSQL_RC_CONTINUE;
 }
 
 
@@ -2455,14 +2550,6 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
 
 			if (rc != PLPGSQL_RC_OK)
 			{
-				/*
-				 * We're aborting the loop, so cleanup and set FOUND.
-				 * (This code should match the code after the loop.)
-				 */
-				SPI_freetuptable(tuptab);
-				SPI_cursor_close(portal);
-				exec_set_found(estate, found);
-
 				if (rc == PLPGSQL_RC_EXIT)
 				{
 					if (estate->exitlabel == NULL)
@@ -2483,6 +2570,33 @@ exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
 					 * recurse upward.
 					 */
 				}
+				else if (rc == PLPGSQL_RC_CONTINUE)
+				{
+					if (estate->exitlabel == NULL)
+						/* unlabelled continue, continue the current loop */
+						continue;
+					else if (stmt->label != NULL &&
+							 strcmp(stmt->label, estate->exitlabel) == 0)
+					{
+						/* labelled continue, matches the current stmt's label */
+						estate->exitlabel = NULL;
+						continue;
+					}
+
+					/*
+					 * otherwise, we process a labelled continue that
+					 * does not match the current statement's label,
+					 * so propagate RC_CONTINUE upward in the stack.
+					 */
+				}
+
+				/*
+				 * We're aborting the loop, so cleanup and set FOUND.
+				 * (This code should match the code after the loop.)
+				 */
+				SPI_freetuptable(tuptab);
+				SPI_cursor_close(portal);
+				exec_set_found(estate, found);
 
 				return rc;
 			}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index 23b2b3e71a1..03280c94b12 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.44 2005/06/19 01:06:12 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.45 2005/06/22 01:35:02 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -845,7 +845,8 @@ static void
 dump_exit(PLpgSQL_stmt_exit *stmt)
 {
 	dump_ind();
-	printf("EXIT lbl='%s'", stmt->label);
+	printf("%s label='%s'",
+		   stmt->is_exit ? "EXIT" : "CONTINUE", stmt->label);
 	if (stmt->cond != NULL)
 	{
 		printf(" WHEN ");
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index a724df67963..3615b3bf066 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -3,7 +3,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.63 2005/06/14 06:43:14 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.64 2005/06/22 01:35:02 neilc Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -125,7 +125,8 @@ enum
 {
 	PLPGSQL_RC_OK,
 	PLPGSQL_RC_EXIT,
-	PLPGSQL_RC_RETURN
+	PLPGSQL_RC_RETURN,
+	PLPGSQL_RC_CONTINUE
 };
 
 /* ----------
@@ -485,9 +486,10 @@ typedef struct
 
 
 typedef struct
-{								/* EXIT statement			*/
+{								/* EXIT or CONTINUE statement			*/
 	int			cmd_type;
 	int			lineno;
+	bool		is_exit;		/* Is this an exit or a continue? */
 	char	   *label;
 	PLpgSQL_expr *cond;
 } PLpgSQL_stmt_exit;
@@ -610,7 +612,8 @@ typedef struct
 	bool		readonly_func;
 
 	TupleDesc	rettupdesc;
-	char	   *exitlabel;
+	char	   *exitlabel;		/* the "target" label of the current
+								 * EXIT or CONTINUE stmt, if any */
 
 	Tuplestorestate *tuple_store;		/* SRFs accumulate results here */
 	MemoryContext tuple_store_cxt;
diff --git a/src/pl/plpgsql/src/scan.l b/src/pl/plpgsql/src/scan.l
index f9295196439..680a58fc018 100644
--- a/src/pl/plpgsql/src/scan.l
+++ b/src/pl/plpgsql/src/scan.l
@@ -4,7 +4,7 @@
  *			  procedural language
  *
  * IDENTIFICATION
- *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.40 2005/03/11 19:13:43 momjian Exp $
+ *    $PostgreSQL: pgsql/src/pl/plpgsql/src/scan.l,v 1.41 2005/06/22 01:35:02 neilc Exp $
  *
  *    This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -139,6 +139,7 @@ alias			{ return K_ALIAS;			}
 begin			{ return K_BEGIN;			}
 close			{ return K_CLOSE;			}
 constant		{ return K_CONSTANT;		}
+continue		{ return K_CONTINUE;		}
 cursor			{ return K_CURSOR;			}
 debug			{ return K_DEBUG;			}
 declare			{ return K_DECLARE;			}
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 21101958ab4..23ef2d3f2ed 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2491,3 +2491,140 @@ NOTICE:  {10,20,30}; 20; xyz; xyzabc; (10,aaa,,30); <NULL>
 (1 row)
 
 drop function raise_exprs();
+-- continue statement 
+create table conttesttbl(idx serial, v integer);
+NOTICE:  CREATE TABLE will create implicit sequence "conttesttbl_idx_seq" for serial column "conttesttbl.idx"
+insert into conttesttbl(v) values(10);
+insert into conttesttbl(v) values(20);
+insert into conttesttbl(v) values(30);
+insert into conttesttbl(v) values(40);
+create function continue_test1() returns void as $$
+declare _i integer = 0; _r record;
+begin
+  raise notice '---1---';
+  loop
+    _i := _i + 1;
+    raise notice '%', _i;
+    continue when _i < 10;
+    exit;
+  end loop;
+
+  raise notice '---2---';
+  <<lbl>>
+  loop
+    _i := _i - 1;
+    loop
+      raise notice '%', _i;
+      continue lbl when _i > 0;
+      exit lbl;
+    end loop;
+  end loop;
+
+  raise notice '---3---';
+  <<the_loop>>
+  while _i < 10 loop
+    _i := _i + 1;
+    continue the_loop when _i % 2 = 0;
+    raise notice '%', _i;
+  end loop;
+
+  raise notice '---4---';
+  for _i in 1..10 loop
+    begin
+      -- applies to outer loop, not the nested begin block
+      continue when _i < 5; 
+      raise notice '%', _i;
+    end;
+  end loop;
+
+  raise notice '---5---';
+  for _r in select * from conttesttbl loop
+    continue when _r.v <= 20;
+    raise notice '%', _r.v;
+  end loop;
+
+  raise notice '---6---';
+  for _r in execute 'select * from conttesttbl' loop
+    continue when _r.v <= 20;
+    raise notice '%', _r.v;
+  end loop;  
+end; $$ language plpgsql;
+select continue_test1();
+NOTICE:  ---1---
+NOTICE:  1
+NOTICE:  2
+NOTICE:  3
+NOTICE:  4
+NOTICE:  5
+NOTICE:  6
+NOTICE:  7
+NOTICE:  8
+NOTICE:  9
+NOTICE:  10
+NOTICE:  ---2---
+NOTICE:  9
+NOTICE:  8
+NOTICE:  7
+NOTICE:  6
+NOTICE:  5
+NOTICE:  4
+NOTICE:  3
+NOTICE:  2
+NOTICE:  1
+NOTICE:  0
+NOTICE:  ---3---
+NOTICE:  1
+NOTICE:  3
+NOTICE:  5
+NOTICE:  7
+NOTICE:  9
+NOTICE:  ---4---
+NOTICE:  5
+NOTICE:  6
+NOTICE:  7
+NOTICE:  8
+NOTICE:  9
+NOTICE:  10
+NOTICE:  ---5---
+NOTICE:  30
+NOTICE:  40
+NOTICE:  ---6---
+NOTICE:  30
+NOTICE:  40
+ continue_test1 
+----------------
+ 
+(1 row)
+
+-- CONTINUE is only legal inside a loop
+create function continue_test2() returns void as $$
+begin
+    begin
+        continue;
+    end;
+    return;
+end;
+$$ language plpgsql;
+-- should fail
+select continue_test2();
+ERROR:  CONTINUE cannot be used outside a loop
+CONTEXT:  PL/pgSQL function "continue_test2"
+-- CONTINUE can't reference the label of a named block
+create function continue_test3() returns void as $$
+begin
+    <<begin_block1>>
+    begin
+        loop
+            continue begin_block1;
+        end loop;
+    end;
+end;
+$$ language plpgsql;
+-- should fail
+select continue_test3();
+ERROR:  CONTINUE cannot be used outside a loop
+CONTEXT:  PL/pgSQL function "continue_test3"
+drop function continue_test1();
+drop function continue_test2();
+drop function continue_test3();
+drop table conttesttbl;
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 375eef8959c..393fdc52b0c 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -2112,3 +2112,97 @@ end;$$ language plpgsql;
 
 select raise_exprs();
 drop function raise_exprs();
+
+-- continue statement 
+create table conttesttbl(idx serial, v integer);
+insert into conttesttbl(v) values(10);
+insert into conttesttbl(v) values(20);
+insert into conttesttbl(v) values(30);
+insert into conttesttbl(v) values(40);
+
+create function continue_test1() returns void as $$
+declare _i integer = 0; _r record;
+begin
+  raise notice '---1---';
+  loop
+    _i := _i + 1;
+    raise notice '%', _i;
+    continue when _i < 10;
+    exit;
+  end loop;
+
+  raise notice '---2---';
+  <<lbl>>
+  loop
+    _i := _i - 1;
+    loop
+      raise notice '%', _i;
+      continue lbl when _i > 0;
+      exit lbl;
+    end loop;
+  end loop;
+
+  raise notice '---3---';
+  <<the_loop>>
+  while _i < 10 loop
+    _i := _i + 1;
+    continue the_loop when _i % 2 = 0;
+    raise notice '%', _i;
+  end loop;
+
+  raise notice '---4---';
+  for _i in 1..10 loop
+    begin
+      -- applies to outer loop, not the nested begin block
+      continue when _i < 5; 
+      raise notice '%', _i;
+    end;
+  end loop;
+
+  raise notice '---5---';
+  for _r in select * from conttesttbl loop
+    continue when _r.v <= 20;
+    raise notice '%', _r.v;
+  end loop;
+
+  raise notice '---6---';
+  for _r in execute 'select * from conttesttbl' loop
+    continue when _r.v <= 20;
+    raise notice '%', _r.v;
+  end loop;  
+end; $$ language plpgsql;
+
+select continue_test1();
+
+-- CONTINUE is only legal inside a loop
+create function continue_test2() returns void as $$
+begin
+    begin
+        continue;
+    end;
+    return;
+end;
+$$ language plpgsql;
+
+-- should fail
+select continue_test2();
+
+-- CONTINUE can't reference the label of a named block
+create function continue_test3() returns void as $$
+begin
+    <<begin_block1>>
+    begin
+        loop
+            continue begin_block1;
+        end loop;
+    end;
+end;
+$$ language plpgsql;
+
+-- should fail
+select continue_test3();
+
+drop function continue_test1();
+drop function continue_test2();
+drop function continue_test3();
+drop table conttesttbl;
-- 
GitLab