*** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** *** 1043,1050 **** DELETE ... RETURNING expressions INTO STRIC clause, then target will be set to the first row returned by the query, or to nulls if the query returned no rows. (Note that the first row is not ! well-defined unless you've used ORDER BY.) Any result rows ! after the first row are discarded. You can check the special FOUND variable (see ) to determine whether a row was returned: --- 1043,1053 ---- clause, then target will be set to the first row returned by the query, or to nulls if the query returned no rows. (Note that the first row is not ! well-defined unless you've used ORDER BY.) By default, ! any result rows after the first row are discarded. However, if the ! consistent_into compile option has been enabled and the ! query returns more than one row, the run-time error ! TOO_MANY_ROWS will be reported. You can check the special FOUND variable (see ) to determine whether a row was returned: *************** *** 1172,1178 **** EXECUTE command-string INT record variable is used, it will configure itself to match the result structure automatically). If multiple rows are returned, only the first will be assigned to the INTO ! variable. If no rows are returned, NULL is assigned to the INTO variable(s). If no INTO clause is specified, the query results are discarded. --- 1175,1183 ---- record variable is used, it will configure itself to match the result structure automatically). If multiple rows are returned, only the first will be assigned to the INTO ! variable, unless consistent_into has been ! enabled, in which case TOO_MANY_ROWS is raised. ! If no rows are returned, NULL is assigned to the INTO variable(s). If no INTO clause is specified, the query results are discarded. *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** *** 352,357 **** do_compile(FunctionCallInfo fcinfo, --- 352,358 ---- function->out_param_varno = -1; /* set up for no OUT param */ function->resolve_option = plpgsql_variable_conflict; function->print_strict_params = plpgsql_print_strict_params; + function->consistent_into = plpgsql_consistent_into; if (is_dml_trigger) function->fn_is_trigger = PLPGSQL_DML_TRIGGER; *************** *** 849,854 **** plpgsql_compile_inline(char *proc_source) --- 850,856 ---- function->out_param_varno = -1; /* set up for no OUT param */ function->resolve_option = plpgsql_variable_conflict; function->print_strict_params = plpgsql_print_strict_params; + function->consistent_into = plpgsql_consistent_into; plpgsql_ns_init(); plpgsql_ns_push(func_name); *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** *** 3308,3316 **** exec_stmt_execsql(PLpgSQL_execstate *estate, /* * If we have INTO, then we only need one row back ... but if we have INTO ! * STRICT, ask for two rows, so that we can verify the statement returns ! * only one. INSERT/UPDATE/DELETE are always treated strictly. Without ! * INTO, just run the statement to completion (tcount = 0). * * We could just ask for two rows always when using INTO, but there are * some cases where demanding the extra row costs significant time, eg by --- 3308,3317 ---- /* * If we have INTO, then we only need one row back ... but if we have INTO ! * STRICT or consistent_into is enabled, ask for two rows, so that we can ! * verify the statement returns only one. INSERT/UPDATE/DELETE are always ! * treated strictly. Without INTO, just run the statement to completion ! * (tcount = 0). * * We could just ask for two rows always when using INTO, but there are * some cases where demanding the extra row costs significant time, eg by *************** *** 3319,3325 **** exec_stmt_execsql(PLpgSQL_execstate *estate, */ if (stmt->into) { ! if (stmt->strict || stmt->mod_stmt) tcount = 2; else tcount = 1; --- 3320,3326 ---- */ if (stmt->into) { ! if (stmt->strict || stmt->mod_stmt || estate->func->consistent_into) tcount = 2; else tcount = 1; *************** *** 3439,3445 **** exec_stmt_execsql(PLpgSQL_execstate *estate, } else { ! if (n > 1 && (stmt->strict || stmt->mod_stmt)) { char *errdetail; --- 3440,3446 ---- } else { ! if (n > 1 && (stmt->strict || stmt->mod_stmt || estate->func->consistent_into)) { char *errdetail; *************** *** 3606,3614 **** exec_stmt_dynexecute(PLpgSQL_execstate *estate, elog(ERROR, "unsupported target"); /* ! * If SELECT ... INTO specified STRICT, and the query didn't find ! * exactly one row, throw an error. If STRICT was not specified, then ! * allow the query to find any number of rows. */ if (n == 0) { --- 3607,3615 ---- elog(ERROR, "unsupported target"); /* ! * If SELECT ... INTO specified STRICT or consistent_info is enabled, ! * and the query didn't find exactly one row, throw an error. ! * Otherwise we allow the query to find any number of rows. */ if (n == 0) { *************** *** 3631,3637 **** exec_stmt_dynexecute(PLpgSQL_execstate *estate, } else { ! if (n > 1 && stmt->strict) { char *errdetail; --- 3632,3638 ---- } else { ! if (n > 1 && (stmt->strict || estate->func->consistent_into)) { char *errdetail; *** a/src/pl/plpgsql/src/pl_gram.y --- b/src/pl/plpgsql/src/pl_gram.y *************** *** 185,191 **** static List *read_raise_options(void); %type for_variable %type for_control ! %type any_identifier opt_block_label opt_label option_value %type proc_sect proc_stmts stmt_elsifs stmt_else %type loop_body --- 185,192 ---- %type for_variable %type for_control ! %type any_identifier opt_block_label opt_label ! %type option_value %type proc_sect proc_stmts stmt_elsifs stmt_else %type loop_body *************** *** 253,258 **** static List *read_raise_options(void); --- 254,260 ---- %token K_COLLATE %token K_COLUMN %token K_COLUMN_NAME + %token K_CONSISTENT_INTO %token K_CONSTANT %token K_CONSTRAINT %token K_CONSTRAINT_NAME *************** *** 355,368 **** comp_option : '#' K_OPTION K_DUMP { plpgsql_DumpExecTree = true; } | '#' K_PRINT_STRICT_PARAMS option_value { ! if (strcmp($3, "on") == 0) ! plpgsql_curr_compile->print_strict_params = true; ! else if (strcmp($3, "off") == 0) ! plpgsql_curr_compile->print_strict_params = false; ! else ! elog(ERROR, "unrecognized print_strict_params option %s", $3); } | '#' K_VARIABLE_CONFLICT K_ERROR { --- 357,369 ---- { plpgsql_DumpExecTree = true; } + | '#' K_CONSISTENT_INTO option_value + { + plpgsql_curr_compile->consistent_into = $3; + } | '#' K_PRINT_STRICT_PARAMS option_value { ! plpgsql_curr_compile->print_strict_params = $3; } | '#' K_VARIABLE_CONFLICT K_ERROR { *************** *** 380,390 **** comp_option : '#' K_OPTION K_DUMP option_value : T_WORD { ! $$ = $1.ident; } | unreserved_keyword { ! $$ = pstrdup($1); } opt_semi : --- 381,401 ---- option_value : T_WORD { ! if (strcmp($1.ident, "on") == 0) ! $$ = true; ! else if (strcmp($1.ident, "off") == 0) ! $$ = false; ! else ! elog(ERROR, "unrecognized option_value %s", $1.ident); } | unreserved_keyword { ! if (strcmp($1, "on") == 0) ! $$ = true; ! else if (strcmp($1, "off") == 0) ! $$ = false; ! else ! elog(ERROR, "unrecognized option_value %s", $1); } opt_semi : *** a/src/pl/plpgsql/src/pl_handler.c --- b/src/pl/plpgsql/src/pl_handler.c *************** *** 38,43 **** static const struct config_enum_entry variable_conflict_options[] = { --- 38,44 ---- int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR; bool plpgsql_print_strict_params = false; + bool plpgsql_consistent_into = false; /* Hook for plugins */ PLpgSQL_plugin **plugin_ptr = NULL; *************** *** 75,80 **** _PG_init(void) --- 76,89 ---- false, PGC_USERSET, 0, NULL, NULL, NULL); + DefineCustomBoolVariable("plpgsql.consistent_into", + gettext_noop("Always raise an exception if a statement with INTO returns more than one row."), + NULL, + &plpgsql_consistent_into, + false, + PGC_USERSET, 0, + NULL, NULL, NULL); + EmitWarningsOnPlaceholders("plpgsql"); *** a/src/pl/plpgsql/src/pl_scanner.c --- b/src/pl/plpgsql/src/pl_scanner.c *************** *** 111,116 **** static const ScanKeyword unreserved_keywords[] = { --- 111,117 ---- PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD) PG_KEYWORD("column_name", K_COLUMN_NAME, UNRESERVED_KEYWORD) + PG_KEYWORD("consistent_into", K_CONSISTENT_INTO, UNRESERVED_KEYWORD) PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD) PG_KEYWORD("constraint", K_CONSTRAINT, UNRESERVED_KEYWORD) PG_KEYWORD("constraint_name", K_CONSTRAINT_NAME, UNRESERVED_KEYWORD) *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** *** 738,743 **** typedef struct PLpgSQL_function --- 738,744 ---- PLpgSQL_resolve_option resolve_option; bool print_strict_params; + bool consistent_into; int ndatums; PLpgSQL_datum **datums; *************** *** 880,885 **** extern IdentifierLookup plpgsql_IdentifierLookup; --- 881,887 ---- extern int plpgsql_variable_conflict; extern bool plpgsql_print_strict_params; + extern bool plpgsql_consistent_into; extern bool plpgsql_check_syntax; extern bool plpgsql_DumpExecTree; *** a/src/test/regress/expected/plpgsql.out --- b/src/test/regress/expected/plpgsql.out *************** *** 3208,3213 **** select footest(); --- 3208,3236 ---- ERROR: query returned more than one row DETAIL: parameters: p1 = '2', p3 = 'foo' CONTEXT: PL/pgSQL function footest() line 10 at SQL statement + -- test consistent INTO + set plpgsql.consistent_into to true; + create or replace function footest() returns void as $$ + declare + x int; + begin + -- too many rows + select 1 from foo into x; + end$$ language plpgsql; + select footest(); + ERROR: query returned more than one row + CONTEXT: PL/pgSQL function footest() line 6 at SQL statement + create or replace function footest() returns void as $$ + declare + x int; + begin + -- too many rows + execute $q$select 1 from foo$q$ into x; + end$$ language plpgsql; + select footest(); + ERROR: query returned more than one row + CONTEXT: PL/pgSQL function footest() line 6 at EXECUTE statement + reset plpgsql.consistent_into; -- test scrollable cursor support create function sc_test() returns setof integer as $$ declare *** a/src/test/regress/sql/plpgsql.sql --- b/src/test/regress/sql/plpgsql.sql *************** *** 2689,2694 **** end$$ language plpgsql; --- 2689,2720 ---- select footest(); + -- test consistent INTO + + set plpgsql.consistent_into to true; + + create or replace function footest() returns void as $$ + declare + x int; + begin + -- too many rows + select 1 from foo into x; + end$$ language plpgsql; + + select footest(); + + create or replace function footest() returns void as $$ + declare + x int; + begin + -- too many rows + execute $q$select 1 from foo$q$ into x; + end$$ language plpgsql; + + select footest(); + + reset plpgsql.consistent_into; + -- test scrollable cursor support create function sc_test() returns setof integer as $$