mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-03-07 19:47:50 +08:00
Extra warnings and errors for PL/pgSQL
Infrastructure to allow plpgsql.extra_warnings plpgsql.extra_errors Initial extra checks only for shadowed_variables Marko Tiikkaja and Petr Jelinek Reviewed by Simon Riggs and Pavel Stěhule
This commit is contained in:
parent
f14a6bbedb
commit
7d8f1de1bc
@ -4711,6 +4711,56 @@ a_output := a_output || $$ if v_$$ || referrer_keys.kind || $$ like '$$
|
||||
</variablelist>
|
||||
|
||||
</sect2>
|
||||
<sect2 id="plpgsql-extra-checks">
|
||||
<title>Additional compile-time checks</title>
|
||||
|
||||
<para>
|
||||
To aid the user in finding instances of simple but common problems before
|
||||
they cause harm, <application>PL/PgSQL</> provides additional
|
||||
<replaceable>checks</>. When enabled, depending on the configuration, they
|
||||
can be used to emit either a <literal>WARNING</> or an <literal>ERROR</>
|
||||
during the compilation of a function. A function which has received
|
||||
a <literal>WARNING</> can be executed without producing further messages,
|
||||
so you are advised to test in a separate development environment.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
These additional checks are enabled through the configuration variables
|
||||
<varname>plpgsql.extra_warnings</> for warnings and
|
||||
<varname>plpgsql.extra_errors</> for errors. Both can be set either to
|
||||
a comma-separated list of checks, <literal>"none"</> or <literal>"all"</>.
|
||||
The default is <literal>"none"</>. Currently the list of available checks
|
||||
includes only one:
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>shadowed_variables</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Checks if a declaration shadows a previously defined variable.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
The following example shows the effect of <varname>plpgsql.extra_warnings</>
|
||||
set to <varname>shadowed_variables</>:
|
||||
<programlisting>
|
||||
SET plpgsql.extra_warnings TO 'shadowed_variables';
|
||||
|
||||
CREATE FUNCTION foo(f1 int) RETURNS int AS $$
|
||||
DECLARE
|
||||
f1 int;
|
||||
BEGIN
|
||||
RETURN f1;
|
||||
END
|
||||
$$ LANGUAGE plpgsql;
|
||||
WARNING: variable "f1" shadows a previously defined variable
|
||||
LINE 3: f1 int;
|
||||
^
|
||||
CREATE FUNCTION
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
<!-- **** Porting from Oracle PL/SQL **** -->
|
||||
|
@ -352,6 +352,9 @@ do_compile(FunctionCallInfo fcinfo,
|
||||
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;
|
||||
/* only promote extra warnings and errors at CREATE FUNCTION time */
|
||||
function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
|
||||
function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
|
||||
|
||||
if (is_dml_trigger)
|
||||
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
|
||||
@ -849,6 +852,9 @@ plpgsql_compile_inline(char *proc_source)
|
||||
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;
|
||||
/* don't do extra validation for inline code as we don't want to add spam at runtime */
|
||||
function->extra_warnings = 0;
|
||||
function->extra_errors = 0;
|
||||
|
||||
plpgsql_ns_init();
|
||||
plpgsql_ns_push(func_name);
|
||||
|
@ -727,6 +727,21 @@ decl_varname : T_WORD
|
||||
$1.ident, NULL, NULL,
|
||||
NULL) != NULL)
|
||||
yyerror("duplicate declaration");
|
||||
|
||||
if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
|
||||
plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
|
||||
{
|
||||
PLpgSQL_nsitem *nsi;
|
||||
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
||||
$1.ident, NULL, NULL, NULL);
|
||||
if (nsi != NULL)
|
||||
ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
|
||||
(errcode(ERRCODE_DUPLICATE_ALIAS),
|
||||
errmsg("variable \"%s\" shadows a previously defined variable",
|
||||
$1.ident),
|
||||
parser_errposition(@1)));
|
||||
}
|
||||
|
||||
}
|
||||
| unreserved_keyword
|
||||
{
|
||||
@ -740,6 +755,21 @@ decl_varname : T_WORD
|
||||
$1, NULL, NULL,
|
||||
NULL) != NULL)
|
||||
yyerror("duplicate declaration");
|
||||
|
||||
if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
|
||||
plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
|
||||
{
|
||||
PLpgSQL_nsitem *nsi;
|
||||
nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
|
||||
$1, NULL, NULL, NULL);
|
||||
if (nsi != NULL)
|
||||
ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
|
||||
(errcode(ERRCODE_DUPLICATE_ALIAS),
|
||||
errmsg("variable \"%s\" shadows a previously defined variable",
|
||||
$1),
|
||||
parser_errposition(@1)));
|
||||
}
|
||||
|
||||
}
|
||||
;
|
||||
|
||||
|
@ -25,6 +25,11 @@
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
static bool plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source);
|
||||
static void plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra);
|
||||
static void plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra);
|
||||
|
||||
PG_MODULE_MAGIC;
|
||||
|
||||
/* Custom GUC variable */
|
||||
@ -39,10 +44,89 @@ int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
|
||||
|
||||
bool plpgsql_print_strict_params = false;
|
||||
|
||||
char *plpgsql_extra_warnings_string = NULL;
|
||||
char *plpgsql_extra_errors_string = NULL;
|
||||
int plpgsql_extra_warnings;
|
||||
int plpgsql_extra_errors;
|
||||
|
||||
/* Hook for plugins */
|
||||
PLpgSQL_plugin **plugin_ptr = NULL;
|
||||
|
||||
|
||||
static bool
|
||||
plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
|
||||
{
|
||||
char *rawstring;
|
||||
List *elemlist;
|
||||
ListCell *l;
|
||||
int extrachecks = 0;
|
||||
int *myextra;
|
||||
|
||||
if (pg_strcasecmp(*newvalue, "all") == 0)
|
||||
extrachecks = PLPGSQL_XCHECK_ALL;
|
||||
else if (pg_strcasecmp(*newvalue, "none") == 0)
|
||||
extrachecks = PLPGSQL_XCHECK_NONE;
|
||||
else
|
||||
{
|
||||
/* Need a modifiable copy of string */
|
||||
rawstring = pstrdup(*newvalue);
|
||||
|
||||
/* Parse string into list of identifiers */
|
||||
if (!SplitIdentifierString(rawstring, ',', &elemlist))
|
||||
{
|
||||
/* syntax error in list */
|
||||
GUC_check_errdetail("List syntax is invalid.");
|
||||
pfree(rawstring);
|
||||
list_free(elemlist);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach(l, elemlist)
|
||||
{
|
||||
char *tok = (char *) lfirst(l);
|
||||
|
||||
if (pg_strcasecmp(tok, "shadowed_variables") == 0)
|
||||
extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
|
||||
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);
|
||||
pfree(rawstring);
|
||||
list_free(elemlist);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
|
||||
pfree(rawstring);
|
||||
list_free(elemlist);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pfree(rawstring);
|
||||
list_free(elemlist);
|
||||
}
|
||||
|
||||
myextra = (int *) malloc(sizeof(int));
|
||||
*myextra = extrachecks;
|
||||
*extra = (void *) myextra;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra)
|
||||
{
|
||||
plpgsql_extra_warnings = *((int *) extra);
|
||||
}
|
||||
|
||||
static void
|
||||
plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra)
|
||||
{
|
||||
plpgsql_extra_errors = *((int *) extra);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* _PG_init() - library load-time initialization
|
||||
*
|
||||
@ -76,6 +160,26 @@ _PG_init(void)
|
||||
PGC_USERSET, 0,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
DefineCustomStringVariable("plpgsql.extra_warnings",
|
||||
gettext_noop("List of programming constructs which should produce a warning."),
|
||||
NULL,
|
||||
&plpgsql_extra_warnings_string,
|
||||
"none",
|
||||
PGC_USERSET, GUC_LIST_INPUT,
|
||||
plpgsql_extra_checks_check_hook,
|
||||
plpgsql_extra_warnings_assign_hook,
|
||||
NULL);
|
||||
|
||||
DefineCustomStringVariable("plpgsql.extra_errors",
|
||||
gettext_noop("List of programming constructs which should produce an error."),
|
||||
NULL,
|
||||
&plpgsql_extra_errors_string,
|
||||
"none",
|
||||
PGC_USERSET, GUC_LIST_INPUT,
|
||||
plpgsql_extra_checks_check_hook,
|
||||
plpgsql_extra_errors_assign_hook,
|
||||
NULL);
|
||||
|
||||
EmitWarningsOnPlaceholders("plpgsql");
|
||||
|
||||
plpgsql_HashTableInit();
|
||||
|
@ -739,6 +739,10 @@ typedef struct PLpgSQL_function
|
||||
|
||||
bool print_strict_params;
|
||||
|
||||
/* extra checks */
|
||||
int extra_warnings;
|
||||
int extra_errors;
|
||||
|
||||
int ndatums;
|
||||
PLpgSQL_datum **datums;
|
||||
PLpgSQL_stmt_block *action;
|
||||
@ -881,6 +885,14 @@ extern int plpgsql_variable_conflict;
|
||||
|
||||
extern bool plpgsql_print_strict_params;
|
||||
|
||||
/* extra compile-time checks */
|
||||
#define PLPGSQL_XCHECK_NONE 0
|
||||
#define PLPGSQL_XCHECK_SHADOWVAR 1
|
||||
#define PLPGSQL_XCHECK_ALL ((int) ~0)
|
||||
|
||||
extern int plpgsql_extra_warnings;
|
||||
extern int plpgsql_extra_errors;
|
||||
|
||||
extern bool plpgsql_check_syntax;
|
||||
extern bool plpgsql_DumpExecTree;
|
||||
|
||||
|
@ -3208,6 +3208,128 @@ select footest();
|
||||
ERROR: query returned more than one row
|
||||
DETAIL: parameters: p1 = '2', p3 = 'foo'
|
||||
CONTEXT: PL/pgSQL function footest() line 10 at SQL statement
|
||||
-- test warnings and errors
|
||||
set plpgsql.extra_warnings to 'all';
|
||||
set plpgsql.extra_warnings to 'none';
|
||||
set plpgsql.extra_errors to 'all';
|
||||
set plpgsql.extra_errors to 'none';
|
||||
-- test warnings when shadowing a variable
|
||||
set plpgsql.extra_warnings to 'shadowed_variables';
|
||||
-- simple shadowing of input and output parameters
|
||||
create or replace function shadowtest(in1 int)
|
||||
returns table (out1 int) as $$
|
||||
declare
|
||||
in1 int;
|
||||
out1 int;
|
||||
begin
|
||||
end
|
||||
$$ language plpgsql;
|
||||
WARNING: variable "in1" shadows a previously defined variable
|
||||
LINE 4: in1 int;
|
||||
^
|
||||
WARNING: variable "out1" shadows a previously defined variable
|
||||
LINE 5: out1 int;
|
||||
^
|
||||
select shadowtest(1);
|
||||
shadowtest
|
||||
------------
|
||||
(0 rows)
|
||||
|
||||
set plpgsql.extra_warnings to 'shadowed_variables';
|
||||
select shadowtest(1);
|
||||
shadowtest
|
||||
------------
|
||||
(0 rows)
|
||||
|
||||
create or replace function shadowtest(in1 int)
|
||||
returns table (out1 int) as $$
|
||||
declare
|
||||
in1 int;
|
||||
out1 int;
|
||||
begin
|
||||
end
|
||||
$$ language plpgsql;
|
||||
WARNING: variable "in1" shadows a previously defined variable
|
||||
LINE 4: in1 int;
|
||||
^
|
||||
WARNING: variable "out1" shadows a previously defined variable
|
||||
LINE 5: out1 int;
|
||||
^
|
||||
select shadowtest(1);
|
||||
shadowtest
|
||||
------------
|
||||
(0 rows)
|
||||
|
||||
drop function shadowtest(int);
|
||||
-- shadowing in a second DECLARE block
|
||||
create or replace function shadowtest()
|
||||
returns void as $$
|
||||
declare
|
||||
f1 int;
|
||||
begin
|
||||
declare
|
||||
f1 int;
|
||||
begin
|
||||
end;
|
||||
end$$ language plpgsql;
|
||||
WARNING: variable "f1" shadows a previously defined variable
|
||||
LINE 7: f1 int;
|
||||
^
|
||||
drop function shadowtest();
|
||||
-- several levels of shadowing
|
||||
create or replace function shadowtest(in1 int)
|
||||
returns void as $$
|
||||
declare
|
||||
in1 int;
|
||||
begin
|
||||
declare
|
||||
in1 int;
|
||||
begin
|
||||
end;
|
||||
end$$ language plpgsql;
|
||||
WARNING: variable "in1" shadows a previously defined variable
|
||||
LINE 4: in1 int;
|
||||
^
|
||||
WARNING: variable "in1" shadows a previously defined variable
|
||||
LINE 7: in1 int;
|
||||
^
|
||||
drop function shadowtest(int);
|
||||
-- shadowing in cursor definitions
|
||||
create or replace function shadowtest()
|
||||
returns void as $$
|
||||
declare
|
||||
f1 int;
|
||||
c1 cursor (f1 int) for select 1;
|
||||
begin
|
||||
end$$ language plpgsql;
|
||||
WARNING: variable "f1" shadows a previously defined variable
|
||||
LINE 5: c1 cursor (f1 int) for select 1;
|
||||
^
|
||||
drop function shadowtest();
|
||||
-- test errors when shadowing a variable
|
||||
set plpgsql.extra_errors to 'shadowed_variables';
|
||||
create or replace function shadowtest(f1 int)
|
||||
returns boolean as $$
|
||||
declare f1 int; begin return 1; end $$ language plpgsql;
|
||||
ERROR: variable "f1" shadows a previously defined variable
|
||||
LINE 3: declare f1 int; begin return 1; end $$ language plpgsql;
|
||||
^
|
||||
select shadowtest(1);
|
||||
ERROR: function shadowtest(integer) does not exist
|
||||
LINE 1: select shadowtest(1);
|
||||
^
|
||||
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
|
||||
reset plpgsql.extra_errors;
|
||||
reset plpgsql.extra_warnings;
|
||||
create or replace function shadowtest(f1 int)
|
||||
returns boolean as $$
|
||||
declare f1 int; begin return 1; end $$ language plpgsql;
|
||||
select shadowtest(1);
|
||||
shadowtest
|
||||
------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
-- test scrollable cursor support
|
||||
create function sc_test() returns setof integer as $$
|
||||
declare
|
||||
|
@ -2689,6 +2689,95 @@ end$$ language plpgsql;
|
||||
|
||||
select footest();
|
||||
|
||||
-- test warnings and errors
|
||||
set plpgsql.extra_warnings to 'all';
|
||||
set plpgsql.extra_warnings to 'none';
|
||||
set plpgsql.extra_errors to 'all';
|
||||
set plpgsql.extra_errors to 'none';
|
||||
|
||||
-- test warnings when shadowing a variable
|
||||
|
||||
set plpgsql.extra_warnings to 'shadowed_variables';
|
||||
|
||||
-- simple shadowing of input and output parameters
|
||||
create or replace function shadowtest(in1 int)
|
||||
returns table (out1 int) as $$
|
||||
declare
|
||||
in1 int;
|
||||
out1 int;
|
||||
begin
|
||||
end
|
||||
$$ language plpgsql;
|
||||
select shadowtest(1);
|
||||
|
||||
set plpgsql.extra_warnings to 'shadowed_variables';
|
||||
select shadowtest(1);
|
||||
create or replace function shadowtest(in1 int)
|
||||
returns table (out1 int) as $$
|
||||
declare
|
||||
in1 int;
|
||||
out1 int;
|
||||
begin
|
||||
end
|
||||
$$ language plpgsql;
|
||||
select shadowtest(1);
|
||||
drop function shadowtest(int);
|
||||
|
||||
-- shadowing in a second DECLARE block
|
||||
create or replace function shadowtest()
|
||||
returns void as $$
|
||||
declare
|
||||
f1 int;
|
||||
begin
|
||||
declare
|
||||
f1 int;
|
||||
begin
|
||||
end;
|
||||
end$$ language plpgsql;
|
||||
drop function shadowtest();
|
||||
|
||||
-- several levels of shadowing
|
||||
create or replace function shadowtest(in1 int)
|
||||
returns void as $$
|
||||
declare
|
||||
in1 int;
|
||||
begin
|
||||
declare
|
||||
in1 int;
|
||||
begin
|
||||
end;
|
||||
end$$ language plpgsql;
|
||||
drop function shadowtest(int);
|
||||
|
||||
-- shadowing in cursor definitions
|
||||
create or replace function shadowtest()
|
||||
returns void as $$
|
||||
declare
|
||||
f1 int;
|
||||
c1 cursor (f1 int) for select 1;
|
||||
begin
|
||||
end$$ language plpgsql;
|
||||
drop function shadowtest();
|
||||
|
||||
-- test errors when shadowing a variable
|
||||
|
||||
set plpgsql.extra_errors to 'shadowed_variables';
|
||||
|
||||
create or replace function shadowtest(f1 int)
|
||||
returns boolean as $$
|
||||
declare f1 int; begin return 1; end $$ language plpgsql;
|
||||
|
||||
select shadowtest(1);
|
||||
|
||||
reset plpgsql.extra_errors;
|
||||
reset plpgsql.extra_warnings;
|
||||
|
||||
create or replace function shadowtest(f1 int)
|
||||
returns boolean as $$
|
||||
declare f1 int; begin return 1; end $$ language plpgsql;
|
||||
|
||||
select shadowtest(1);
|
||||
|
||||
-- test scrollable cursor support
|
||||
|
||||
create function sc_test() returns setof integer as $$
|
||||
|
Loading…
Reference in New Issue
Block a user