Commit 167075be authored by Tomas Vondra's avatar Tomas Vondra

Add strict_multi_assignment and too_many_rows plpgsql checks

Until now shadowed_variables was the only plpgsql check supported by
plpgsql.extra_warnings and plpgsql.extra_errors.  This patch introduces
two new checks - strict_multi_assignment and too_many_rows.  Unlike
shadowed_variables, these new checks are enforced at run-time.

strict_multi_assignment checks that commands allowing multi-assignment
(for example SELECT INTO) have the same number of sources and targets.
too_many_rows checks that queries with an INTO clause return one row
exactly.

These checks are aimed at cases that are technically valid and allowed,
but are often a sign of a bug.  Therefore those checks are expected to
be enabled primarily in development and testing environments.

Author: Pavel Stehule
Reviewed-by: Stephen Frost, Tomas Vondra
Discussion: https://www.postgresql.org/message-id/flat/CAFj8pRA2kKRDKpUNwLY0GeG1OqOp+tLS2yQA1V41gzuSz-hCng@mail.gmail.com
parent 2d306759
...@@ -5034,7 +5034,7 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$ ...@@ -5034,7 +5034,7 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</sect2> </sect2>
<sect2 id="plpgsql-extra-checks"> <sect2 id="plpgsql-extra-checks">
<title>Additional Compile-time Checks</title> <title>Additional Compile-time and Run-time Checks</title>
<para> <para>
To aid the user in finding instances of simple but common problems before To aid the user in finding instances of simple but common problems before
...@@ -5046,13 +5046,19 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$ ...@@ -5046,13 +5046,19 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
so you are advised to test in a separate development environment. so you are advised to test in a separate development environment.
</para> </para>
<para>
Setting <varname>plpgsql.extra_warnings</varname>, or
<varname>plpgsql.extra_errors</varname>, as appropriate, to <literal>"all"</literal>
is encouraged in development and/or testing environments.
</para>
<para> <para>
These additional checks are enabled through the configuration variables These additional checks are enabled through the configuration variables
<varname>plpgsql.extra_warnings</varname> for warnings and <varname>plpgsql.extra_warnings</varname> for warnings and
<varname>plpgsql.extra_errors</varname> for errors. Both can be set either to <varname>plpgsql.extra_errors</varname> for errors. Both can be set either to
a comma-separated list of checks, <literal>"none"</literal> or <literal>"all"</literal>. a comma-separated list of checks, <literal>"none"</literal> or
The default is <literal>"none"</literal>. Currently the list of available checks <literal>"all"</literal>. The default is <literal>"none"</literal>. Currently
includes only one: the list of available checks includes:
<variablelist> <variablelist>
<varlistentry> <varlistentry>
<term><varname>shadowed_variables</varname></term> <term><varname>shadowed_variables</varname></term>
...@@ -5062,6 +5068,38 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$ ...@@ -5062,6 +5068,38 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><varname>strict_multi_assignment</varname></term>
<listitem>
<para>
Some <application>PL/PgSQL</application> commands allow assigning
values to more than one variable at a time, such as
<command>SELECT INTO</command>. Typically, the number of target
variables and the number of source variables should match, though
<application>PL/PgSQL</application> will use <literal>NULL</literal>
for missing values and extra variables are ignored. Enabling this
check will cause <application>PL/PgSQL</application> to throw a
<literal>WARNING</literal> or <literal>ERROR</literal> whenever the
number of target variables and the number of source variables are
different.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>too_many_rows</varname></term>
<listitem>
<para>
Enabling this check will cause <application>PL/PgSQL</application> to
check if a given query returns more than one row when an
<literal>INTO</literal> clause is used. As an <literal>INTO</literal>
statement will only ever use one row, having a query return multiple
rows is generally either inefficient and/or nondeterministic and
therefore is likely an error.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
The following example shows the effect of <varname>plpgsql.extra_warnings</varname> The following example shows the effect of <varname>plpgsql.extra_warnings</varname>
...@@ -5080,6 +5118,39 @@ WARNING: variable "f1" shadows a previously defined variable ...@@ -5080,6 +5118,39 @@ WARNING: variable "f1" shadows a previously defined variable
LINE 3: f1 int; LINE 3: f1 int;
^ ^
CREATE FUNCTION CREATE FUNCTION
</programlisting>
The below example shows the effects of setting
<varname>plpgsql.extra_warnings</varname> to
<varname>strict_multi_assignment</varname>:
<programlisting>
SET plpgsql.extra_warnings TO 'strict_multi_assignment';
CREATE OR REPLACE FUNCTION public.foo()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
x int;
y int;
BEGIN
SELECT 1 INTO x, y;
SELECT 1, 2 INTO x, y;
SELECT 1, 2, 3 INTO x, y;
END;
$$;
SELECT foo();
WARNING: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_warnings is active.
HINT: Make sure the query returns the exact list of columns.
WARNING: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_warnings is active.
HINT: Make sure the query returns the exact list of columns.
foo
-----
(1 row)
</programlisting> </programlisting>
</para> </para>
</sect2> </sect2>
......
...@@ -4020,6 +4020,12 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, ...@@ -4020,6 +4020,12 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
long tcount; long tcount;
int rc; int rc;
PLpgSQL_expr *expr = stmt->sqlstmt; PLpgSQL_expr *expr = stmt->sqlstmt;
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;
/* /*
* On the first call for this statement generate the plan, and detect * On the first call for this statement generate the plan, and detect
...@@ -4059,9 +4065,10 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, ...@@ -4059,9 +4065,10 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
/* /*
* If we have INTO, then we only need one row back ... but if we have INTO * 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 * STRICT or extra check too_many_rows, ask for two rows, so that we can
* only one. INSERT/UPDATE/DELETE are always treated strictly. Without * verify the statement returns only one. INSERT/UPDATE/DELETE are always
* INTO, just run the statement to completion (tcount = 0). * 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 * 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 * some cases where demanding the extra row costs significant time, eg by
...@@ -4070,7 +4077,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, ...@@ -4070,7 +4077,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
*/ */
if (stmt->into) if (stmt->into)
{ {
if (stmt->strict || stmt->mod_stmt) if (stmt->strict || stmt->mod_stmt || too_many_rows_level)
tcount = 2; tcount = 2;
else else
tcount = 1; tcount = 1;
...@@ -4187,19 +4194,23 @@ exec_stmt_execsql(PLpgSQL_execstate *estate, ...@@ -4187,19 +4194,23 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
} }
else else
{ {
if (n > 1 && (stmt->strict || stmt->mod_stmt)) if (n > 1 && (stmt->strict || stmt->mod_stmt || too_many_rows_level))
{ {
char *errdetail; char *errdetail;
int errlevel;
if (estate->func->print_strict_params) if (estate->func->print_strict_params)
errdetail = format_expr_params(estate, expr); errdetail = format_expr_params(estate, expr);
else else
errdetail = NULL; errdetail = NULL;
ereport(ERROR, errlevel = (stmt->strict || stmt->mod_stmt) ? ERROR : too_many_rows_level;
ereport(errlevel,
(errcode(ERRCODE_TOO_MANY_ROWS), (errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("query returned more than one row"), 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 */ /* Put the first result row into the target */
exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc); exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc);
...@@ -6835,6 +6846,19 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate, ...@@ -6835,6 +6846,19 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
int td_natts = tupdesc ? tupdesc->natts : 0; int td_natts = tupdesc ? tupdesc->natts : 0;
int fnum; int fnum;
int anum; int anum;
int strict_multiassignment_level = 0;
/*
* The extra check strict strict_multi_assignment can be active,
* only when input tupdesc is specified.
*/
if (tupdesc != NULL)
{
if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
strict_multiassignment_level = ERROR;
else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
strict_multiassignment_level = WARNING;
}
/* Handle RECORD-target case */ /* Handle RECORD-target case */
if (target->dtype == PLPGSQL_DTYPE_REC) if (target->dtype == PLPGSQL_DTYPE_REC)
...@@ -6913,10 +6937,23 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate, ...@@ -6913,10 +6937,23 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
} }
else else
{ {
/* no source for destination column */
value = (Datum) 0; value = (Datum) 0;
isnull = true; isnull = true;
valtype = UNKNOWNOID; valtype = UNKNOWNOID;
valtypmod = -1; valtypmod = -1;
/* When source value is missing */
if (strict_multiassignment_level)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment do not match"),
/* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.",
"strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" :
"extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
} }
/* Cast the new value to the right type, if needed. */ /* Cast the new value to the right type, if needed. */
...@@ -6930,6 +6967,29 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate, ...@@ -6930,6 +6967,29 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
newnulls[fnum] = isnull; newnulls[fnum] = isnull;
} }
/*
* When strict_multiassignment extra check is active, then ensure
* there are no unassigned source attributes.
*/
if (strict_multiassignment_level && anum < td_natts)
{
/* skip dropped columns in the source descriptor */
while (anum < td_natts &&
TupleDescAttr(tupdesc, anum)->attisdropped)
anum++;
if (anum < td_natts)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment do not match"),
/* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.",
"strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" :
"extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
}
values = newvalues; values = newvalues;
nulls = newnulls; nulls = newnulls;
} }
...@@ -6986,16 +7046,50 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate, ...@@ -6986,16 +7046,50 @@ exec_move_row_from_fields(PLpgSQL_execstate *estate,
} }
else else
{ {
/* no source for destination column */
value = (Datum) 0; value = (Datum) 0;
isnull = true; isnull = true;
valtype = UNKNOWNOID; valtype = UNKNOWNOID;
valtypmod = -1; valtypmod = -1;
if (strict_multiassignment_level)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment do not match"),
/* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.",
"strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" :
"extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
} }
exec_assign_value(estate, (PLpgSQL_datum *) var, exec_assign_value(estate, (PLpgSQL_datum *) var,
value, isnull, valtype, valtypmod); value, isnull, valtype, valtypmod);
} }
/*
* When strict_multiassignment extra check is active, ensure there
* are no unassigned source attributes.
*/
if (strict_multiassignment_level && anum < td_natts)
{
while (anum < td_natts &&
TupleDescAttr(tupdesc, anum)->attisdropped)
anum++; /* skip dropped column in tuple */
if (anum < td_natts)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment do not match"),
/* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.",
"strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" :
"extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
}
return; return;
} }
......
...@@ -92,6 +92,10 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source) ...@@ -92,6 +92,10 @@ plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
if (pg_strcasecmp(tok, "shadowed_variables") == 0) if (pg_strcasecmp(tok, "shadowed_variables") == 0)
extrachecks |= PLPGSQL_XCHECK_SHADOWVAR; extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
else if (pg_strcasecmp(tok, "too_many_rows") == 0)
extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0) else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
{ {
GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok); GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
......
...@@ -1135,9 +1135,11 @@ extern bool plpgsql_print_strict_params; ...@@ -1135,9 +1135,11 @@ extern bool plpgsql_print_strict_params;
extern bool plpgsql_check_asserts; extern bool plpgsql_check_asserts;
/* extra compile-time checks */ /* extra compile-time and run-time checks */
#define PLPGSQL_XCHECK_NONE 0 #define PLPGSQL_XCHECK_NONE 0
#define PLPGSQL_XCHECK_SHADOWVAR 1 #define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
#define PLPGSQL_XCHECK_ALL ((int) ~0) #define PLPGSQL_XCHECK_ALL ((int) ~0)
extern int plpgsql_extra_warnings; extern int plpgsql_extra_warnings;
......
...@@ -2778,6 +2778,7 @@ begin ...@@ -2778,6 +2778,7 @@ begin
end$$ language plpgsql; end$$ language plpgsql;
select stricttest(); select stricttest();
ERROR: query returned more than one row 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 SQL statement CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement
create or replace function stricttest() returns void as $$ create or replace function stricttest() returns void as $$
declare x record; declare x record;
...@@ -2851,6 +2852,7 @@ begin ...@@ -2851,6 +2852,7 @@ begin
end$$ language plpgsql; end$$ language plpgsql;
select stricttest(); select stricttest();
ERROR: query returned more than one row 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 SQL statement CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement
create or replace function stricttest() returns void as $$ create or replace function stricttest() returns void as $$
declare x record; declare x record;
...@@ -2916,6 +2918,7 @@ end$$ language plpgsql; ...@@ -2916,6 +2918,7 @@ end$$ language plpgsql;
select stricttest(); select stricttest();
ERROR: query returned more than one row ERROR: query returned more than one row
DETAIL: parameters: p1 = '2', p3 = 'foo' DETAIL: parameters: p1 = '2', p3 = 'foo'
HINT: Make sure the query returns a single row, or use LIMIT 1
CONTEXT: PL/pgSQL function stricttest() line 8 at SQL statement CONTEXT: PL/pgSQL function stricttest() line 8 at SQL statement
create or replace function stricttest() returns void as $$ create or replace function stricttest() returns void as $$
declare x record; declare x record;
...@@ -2926,6 +2929,7 @@ begin ...@@ -2926,6 +2929,7 @@ begin
end$$ language plpgsql; end$$ language plpgsql;
select stricttest(); select stricttest();
ERROR: query returned more than one row 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 SQL statement CONTEXT: PL/pgSQL function stricttest() line 5 at SQL statement
create or replace function stricttest() returns void as $$ create or replace function stricttest() returns void as $$
declare x record; declare x record;
...@@ -2973,6 +2977,7 @@ begin ...@@ -2973,6 +2977,7 @@ begin
end$$ language plpgsql; end$$ language plpgsql;
select stricttest(); select stricttest();
ERROR: query returned more than one row 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 10 at SQL statement CONTEXT: PL/pgSQL function stricttest() line 10 at SQL statement
reset plpgsql.print_strict_params; reset plpgsql.print_strict_params;
create or replace function stricttest() returns void as $$ create or replace function stricttest() returns void as $$
...@@ -2990,6 +2995,7 @@ end$$ language plpgsql; ...@@ -2990,6 +2995,7 @@ end$$ language plpgsql;
select stricttest(); select stricttest();
ERROR: query returned more than one row ERROR: query returned more than one row
DETAIL: parameters: p1 = '2', p3 = 'foo' DETAIL: parameters: p1 = '2', p3 = 'foo'
HINT: Make sure the query returns a single row, or use LIMIT 1
CONTEXT: PL/pgSQL function stricttest() line 10 at SQL statement CONTEXT: PL/pgSQL function stricttest() line 10 at SQL statement
-- test warnings and errors -- test warnings and errors
set plpgsql.extra_warnings to 'all'; set plpgsql.extra_warnings to 'all';
...@@ -3113,6 +3119,107 @@ select shadowtest(1); ...@@ -3113,6 +3119,107 @@ select shadowtest(1);
t t
(1 row) (1 row)
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
declare x int;
begin
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;
begin
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 SQL statement
reset plpgsql.extra_errors;
reset plpgsql.extra_warnings;
set plpgsql.extra_warnings to 'strict_multi_assignment';
do $$
declare
x int;
y int;
begin
select 1 into x, y;
select 1,2 into x, y;
select 1,2,3 into x, y;
end
$$;
WARNING: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_warnings is active.
HINT: Make sure the query returns the exact list of columns.
WARNING: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_warnings is active.
HINT: Make sure the query returns the exact list of columns.
set plpgsql.extra_errors to 'strict_multi_assignment';
do $$
declare
x int;
y int;
begin
select 1 into x, y;
select 1,2 into x, y;
select 1,2,3 into x, y;
end
$$;
ERROR: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_errors is active.
HINT: Make sure the query returns the exact list of columns.
CONTEXT: PL/pgSQL function inline_code_block line 6 at SQL statement
create table test_01(a int, b int, c int);
alter table test_01 drop column a;
-- the check is active only when source table is not empty
insert into test_01 values(10,20);
do $$
declare
x int;
y int;
begin
select * from test_01 into x, y; -- should be ok
raise notice 'ok';
select * from test_01 into x; -- should to fail
end;
$$;
NOTICE: ok
ERROR: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_errors is active.
HINT: Make sure the query returns the exact list of columns.
CONTEXT: PL/pgSQL function inline_code_block line 8 at SQL statement
do $$
declare
t test_01;
begin
select 1, 2 into t; -- should be ok
raise notice 'ok';
select 1, 2, 3 into t; -- should fail;
end;
$$;
NOTICE: ok
ERROR: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_errors is active.
HINT: Make sure the query returns the exact list of columns.
CONTEXT: PL/pgSQL function inline_code_block line 7 at SQL statement
do $$
declare
t test_01;
begin
select 1 into t; -- should fail;
end;
$$;
ERROR: number of source and target fields in assignment do not match
DETAIL: strict_multi_assignment check of extra_errors is active.
HINT: Make sure the query returns the exact list of columns.
CONTEXT: PL/pgSQL function inline_code_block line 5 at SQL statement
drop table test_01;
reset plpgsql.extra_errors;
reset plpgsql.extra_warnings;
-- test scrollable cursor support -- test scrollable cursor support
create function sc_test() returns setof integer as $$ create function sc_test() returns setof integer as $$
declare declare
......
...@@ -2627,6 +2627,95 @@ declare f1 int; begin return 1; end $$ language plpgsql; ...@@ -2627,6 +2627,95 @@ declare f1 int; begin return 1; end $$ language plpgsql;
select shadowtest(1); select shadowtest(1);
-- runtime extra checks
set plpgsql.extra_warnings to 'too_many_rows';
do $$
declare x int;
begin
select v from generate_series(1,2) g(v) into x;
end;
$$;
set plpgsql.extra_errors to 'too_many_rows';
do $$
declare x int;
begin
select v from generate_series(1,2) g(v) into x;
end;
$$;
reset plpgsql.extra_errors;
reset plpgsql.extra_warnings;
set plpgsql.extra_warnings to 'strict_multi_assignment';
do $$
declare
x int;
y int;
begin
select 1 into x, y;
select 1,2 into x, y;
select 1,2,3 into x, y;
end
$$;
set plpgsql.extra_errors to 'strict_multi_assignment';
do $$
declare
x int;
y int;
begin
select 1 into x, y;
select 1,2 into x, y;
select 1,2,3 into x, y;
end
$$;
create table test_01(a int, b int, c int);
alter table test_01 drop column a;
-- the check is active only when source table is not empty
insert into test_01 values(10,20);
do $$
declare
x int;
y int;
begin
select * from test_01 into x, y; -- should be ok
raise notice 'ok';
select * from test_01 into x; -- should to fail
end;
$$;
do $$
declare
t test_01;
begin
select 1, 2 into t; -- should be ok
raise notice 'ok';
select 1, 2, 3 into t; -- should fail;
end;
$$;
do $$
declare
t test_01;
begin
select 1 into t; -- should fail;
end;
$$;
drop table test_01;
reset plpgsql.extra_errors;
reset plpgsql.extra_warnings;
-- test scrollable cursor support -- test scrollable cursor support
create function sc_test() returns setof integer as $$ create function sc_test() returns setof integer as $$
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment