mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-30 00:04:49 -04:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									7a28de2052
								
							
						
					
					
						commit
						ebcb4c931d
					
				| @ -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 > 100; | ||||
|     CONTINUE WHEN count < 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><<<replaceable>label</replaceable>>></optional> | ||||
| WHILE <replaceable>expression</replaceable> LOOP | ||||
|  | ||||
| @ -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,12 +1037,13 @@ 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->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; | ||||
|  | ||||
| @ -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,10 +283,21 @@ 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; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * 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,10 +559,21 @@ 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; | ||||
| 
 | ||||
| 		/*
 | ||||
| 		 * 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"))); | ||||
| @ -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) | ||||
| @ -931,9 +957,6 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block) | ||||
| 			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,11 +1232,24 @@ 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; | ||||
| 	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; | ||||
| 			} | ||||
|  | ||||
| @ -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 "); | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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;			} | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user