From c440d39e30699b3d181aaca6a93e031d6fd47435 Mon Sep 17 00:00:00 2001 From: Jeevan Chalke Date: Mon, 2 Nov 2026 16:36:32 +0530 Subject: [PATCH v2] Apply the too_many_rows extra check to EXECUTE ... INTO, too. plpgsql.extra_warnings/extra_errors = 'too_many_rows' is documented as checking whether a query returns more than one row when an INTO clause is used, but only static SQL statements were checked; EXECUTE ... INTO silently used the first row. Checking costs nothing extra here, since EXECUTE fetches all the rows anyway. While here, add the "Make sure the query returns a single row, or use LIMIT 1." hint to the EXECUTE ... INTO STRICT error, so that it matches the error for static SQL. Also document that, with INTO, only the result of the last command in an EXECUTE string is used, and that STRICT and the too_many_rows check look only at that result. --- doc/src/sgml/plpgsql.sgml | 9 +++++++++ src/pl/plpgsql/src/pl_exec.c | 19 +++++++++++++++---- src/test/regress/expected/plpgsql.out | 20 ++++++++++++++++++++ src/test/regress/sql/plpgsql.sql | 14 ++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 561f6e50d63..d75b709c110 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1368,6 +1368,15 @@ EXECUTE command-string INT unless the command produces exactly one row. + + If the command string contains more than one SQL command, only the + result of the last one is assigned to the INTO + variable(s); the results of the earlier commands are discarded. + The STRICT option and the + too_many_rows + extra check likewise look only at the result of the last command. + + The command string can use parameter values, which are referenced in the command as $1, $2, etc. diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index e857daa6745..62fbcc909dd 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -4564,6 +4564,12 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, ParamListInfo paramLI; SPIExecuteOptions options; MemoryContext stmt_mcontext = get_stmt_mcontext(estate); + int too_many_rows_level = 0; + + if (plpgsql_extra_errors & PLPGSQL_XCHECK_TOOMANYROWS) + too_many_rows_level = ERROR; + else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_TOOMANYROWS) + too_many_rows_level = WARNING; /* * First we evaluate the string expression after the EXECUTE keyword. Its @@ -4673,7 +4679,8 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, /* * 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. + * allow the query to find any number of rows, except that the + * too_many_rows extra check may complain about more than one. */ if (n == 0) { @@ -4696,19 +4703,23 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate, } else { - if (n > 1 && stmt->strict) + if (n > 1 && (stmt->strict || too_many_rows_level)) { char *errdetail; + int errlevel; if (estate->func->print_strict_params) errdetail = format_preparedparamsdata(estate, paramLI); else errdetail = NULL; - ereport(ERROR, + errlevel = stmt->strict ? ERROR : too_many_rows_level; + + ereport(errlevel, (errcode(ERRCODE_TOO_MANY_ROWS), errmsg("query returned more than one row"), - errdetail ? errdetail_internal("parameters: %s", errdetail) : 0)); + errdetail ? errdetail_internal("parameters: %s", errdetail) : 0, + errhint("Make sure the query returns a single row, or use LIMIT 1."))); } /* Put the first result row into the target */ diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index b37b2abaf80..21e597c014d 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -2841,6 +2841,7 @@ begin end$$ language plpgsql; select stricttest(); ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE drop function stricttest(); -- test printing parameters after failure due to STRICT @@ -2920,6 +2921,7 @@ end$$ language plpgsql; select stricttest(); ERROR: query returned more than one row DETAIL: parameters: $1 = '1' +HINT: Make sure the query returns a single row, or use LIMIT 1. CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE create or replace function stricttest() returns void as $$ declare x record; @@ -2930,6 +2932,7 @@ begin end$$ language plpgsql; select stricttest(); ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. CONTEXT: PL/pgSQL function stricttest() line 5 at EXECUTE create or replace function stricttest() returns void as $$ -- override the global @@ -3097,6 +3100,14 @@ end; $$; WARNING: query returned more than one row HINT: Make sure the query returns a single row, or use LIMIT 1. +do $$ +declare x int; +begin + execute 'select v from generate_series(1,2) g(v)' into x; +end; +$$; +WARNING: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. set plpgsql.extra_errors to 'too_many_rows'; do $$ declare x int; @@ -3107,6 +3118,15 @@ $$; ERROR: query returned more than one row HINT: Make sure the query returns a single row, or use LIMIT 1. CONTEXT: PL/pgSQL function inline_code_block line 4 at SQL statement +do $$ +declare x int; +begin + execute 'select v from generate_series(1,2) g(v)' into x; +end; +$$; +ERROR: query returned more than one row +HINT: Make sure the query returns a single row, or use LIMIT 1. +CONTEXT: PL/pgSQL function inline_code_block line 4 at EXECUTE reset plpgsql.extra_errors; reset plpgsql.extra_warnings; set plpgsql.extra_warnings to 'strict_multi_assignment'; diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index ae6b67e3e22..9d8de5aaaf0 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -2628,6 +2628,13 @@ begin end; $$; +do $$ +declare x int; +begin + execute 'select v from generate_series(1,2) g(v)' into x; +end; +$$; + set plpgsql.extra_errors to 'too_many_rows'; do $$ @@ -2637,6 +2644,13 @@ begin end; $$; +do $$ +declare x int; +begin + execute 'select v from generate_series(1,2) g(v)' into x; +end; +$$; + reset plpgsql.extra_errors; reset plpgsql.extra_warnings; -- 2.43.0