Hi again,

Here's a patch which allows you to notice those annoying bugs with INTO slightly more quickly. Adding to the next commit phest.


.marko
*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 4730,4736 **** a_output := a_output || $$ if v_$$ || referrer_keys.kind || 
$$ like '$$
    <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>
--- 4730,4736 ----
    <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
!   is as follows:
    <variablelist>
     <varlistentry>
      <term><varname>shadowed_variables</varname></term>
***************
*** 4740,4745 **** a_output := a_output || $$ if v_$$ || referrer_keys.kind || 
$$ like '$$
--- 4740,4755 ----
       </para>
      </listitem>
     </varlistentry>
+    <varlistentry>
+     <term><varname>num_into_expressions</varname></term>
+     <listitem>
+      <para>
+       When possible, checks that the number of expressions specified in the
+       SELECT or RETURNING list of a query matches the number expected by the
+       INTO target.  This check is done on a "best effort" basis.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
  
    The following example shows the effect of <varname>plpgsql.extra_warnings</>
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
***************
*** 22,27 ****
--- 22,28 ----
  #include "parser/scanner.h"
  #include "parser/scansup.h"
  #include "utils/builtins.h"
+ #include "nodes/nodefuncs.h"
  
  
  /* Location tracking support --- simpler than bison's default */
***************
*** 97,103 **** static  PLpgSQL_row             *make_scalar_list1(char 
*initial_name,
                                                                                
   PLpgSQL_datum *initial_datum,
                                                                                
   int lineno, int location);
  static        void                     check_sql_expr(const char *stmt, int 
location,
!                                                                               
int leaderlen);
  static        void                     plpgsql_sql_error_callback(void *arg);
  static        PLpgSQL_type    *parse_datatype(const char *string, int 
location);
  static        void                     check_labels(const char *start_label,
--- 98,104 ----
                                                                                
   PLpgSQL_datum *initial_datum,
                                                                                
   int lineno, int location);
  static        void                     check_sql_expr(const char *stmt, int 
location,
!                                                                               
int leaderlen, PLpgSQL_row *check_row);
  static        void                     plpgsql_sql_error_callback(void *arg);
  static        PLpgSQL_type    *parse_datatype(const char *string, int 
location);
  static        void                     check_labels(const char *start_label,
***************
*** 106,111 **** static void                     check_labels(const char 
*start_label,
--- 107,115 ----
  static        PLpgSQL_expr    *read_cursor_args(PLpgSQL_var *cursor,
                                                                                
  int until, const char *expected);
  static        List                    *read_raise_options(void);
+ static        bool                    find_a_star_walker(Node *node, void 
*context);
+ static        int                             tlist_result_column_count(Node 
*stmt);
+ 
  
  %}
  
***************
*** 1438,1444 **** for_control          : for_variable K_IN
                                                                
PLpgSQL_stmt_fori       *new;
  
                                                                /* Check first 
expression is well-formed */
!                                                               
check_sql_expr(expr1->query, expr1loc, 7);
  
                                                                /* Read and 
check the second one */
                                                                expr2 = 
read_sql_expression2(K_LOOP, K_BY,
--- 1442,1448 ----
                                                                
PLpgSQL_stmt_fori       *new;
  
                                                                /* Check first 
expression is well-formed */
!                                                               
check_sql_expr(expr1->query, expr1loc, 7, NULL);
  
                                                                /* Read and 
check the second one */
                                                                expr2 = 
read_sql_expression2(K_LOOP, K_BY,
***************
*** 1500,1506 **** for_control          : for_variable K_IN
                                                                
pfree(expr1->query);
                                                                expr1->query = 
tmp_query;
  
!                                                               
check_sql_expr(expr1->query, expr1loc, 0);
  
                                                                new = 
palloc0(sizeof(PLpgSQL_stmt_fors));
                                                                new->cmd_type = 
PLPGSQL_STMT_FORS;
--- 1504,1510 ----
                                                                
pfree(expr1->query);
                                                                expr1->query = 
tmp_query;
  
!                                                               
check_sql_expr(expr1->query, expr1loc, 0, NULL);
  
                                                                new = 
palloc0(sizeof(PLpgSQL_stmt_fors));
                                                                new->cmd_type = 
PLPGSQL_STMT_FORS;
***************
*** 2592,2598 **** read_sql_construct(int until,
        pfree(ds.data);
  
        if (valid_sql)
!               check_sql_expr(expr->query, startlocation, strlen(sqlstart));
  
        return expr;
  }
--- 2596,2602 ----
        pfree(ds.data);
  
        if (valid_sql)
!               check_sql_expr(expr->query, startlocation, strlen(sqlstart), 
NULL);
  
        return expr;
  }
***************
*** 2815,2821 **** make_execsql_stmt(int firsttoken, int location)
        expr->ns                        = plpgsql_ns_top();
        pfree(ds.data);
  
!       check_sql_expr(expr->query, location, 0);
  
        execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
        execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
--- 2819,2825 ----
        expr->ns                        = plpgsql_ns_top();
        pfree(ds.data);
  
!       check_sql_expr(expr->query, location, 0, row);
  
        execsql = palloc(sizeof(PLpgSQL_stmt_execsql));
        execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
***************
*** 3409,3421 **** make_scalar_list1(char *initial_name,
   * (typically "SELECT ") prefixed to the source text.  We use this assumption
   * to transpose any error cursor position back to the function source text.
   * If no error cursor is provided, we'll just point at "location".
   */
  static void
! check_sql_expr(const char *stmt, int location, int leaderlen)
  {
        sql_error_callback_arg cbarg;
        ErrorContextCallback  syntax_errcontext;
        MemoryContext oldCxt;
  
        if (!plpgsql_check_syntax)
                return;
--- 3413,3430 ----
   * (typically "SELECT ") prefixed to the source text.  We use this assumption
   * to transpose any error cursor position back to the function source text.
   * If no error cursor is provided, we'll just point at "location".
+  *
+  * If check_row is not NULL and PLPGSQL_XCHECK_NUM_INTO_EXPRESSIONS is 
enabled,
+  * the statement's result column count is compared against it when the exact
+  * number of columns is known and an error or a warning is reported.
   */
  static void
! check_sql_expr(const char *stmt, int location, int leaderlen, PLpgSQL_row 
*check_row)
  {
        sql_error_callback_arg cbarg;
        ErrorContextCallback  syntax_errcontext;
        MemoryContext oldCxt;
+       List *raw_parsetree_list;
  
        if (!plpgsql_check_syntax)
                return;
***************
*** 3429,3441 **** check_sql_expr(const char *stmt, int location, int leaderlen)
        error_context_stack = &syntax_errcontext;
  
        oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
!       (void) raw_parser(stmt);
        MemoryContextSwitchTo(oldCxt);
  
        /* Restore former ereport callback */
        error_context_stack = syntax_errcontext.previous;
  }
  
  static void
  plpgsql_sql_error_callback(void *arg)
  {
--- 3438,3527 ----
        error_context_stack = &syntax_errcontext;
  
        oldCxt = MemoryContextSwitchTo(compile_tmp_cxt);
!       raw_parsetree_list = raw_parser(stmt);
! 
!       if (check_row != NULL &&
!               (plpgsql_curr_compile->extra_warnings & 
PLPGSQL_XCHECK_NUM_INTO_EXPRESSIONS ||
!                plpgsql_curr_compile->extra_errors & 
PLPGSQL_XCHECK_NUM_INTO_EXPRESSIONS))
!       {
!               Node *raw_parse_tree;
!               int ncols;
!               int fnum;
!               int expected_ncols = 0;
! 
!               for (fnum = 0; fnum < check_row->nfields; fnum++)
!               {
!                       if (check_row->varnos[fnum] < 0)
!                               continue;
!                       expected_ncols++;
!               }
! 
!               raw_parse_tree = linitial(raw_parsetree_list);
!               ncols = tlist_result_column_count(raw_parse_tree);
!               if (ncols >= 0 && ncols != expected_ncols)
!                       ereport(plpgsql_curr_compile->extra_errors & 
PLPGSQL_XCHECK_NUM_INTO_EXPRESSIONS ? ERROR : WARNING,
!                               (errcode(ERRCODE_SYNTAX_ERROR),
!                               errmsg("the number of result expressions does 
not match that expected by the INTO target"),
!                               errdetail("INTO target expects %d expressions, 
but the query returns %d", expected_ncols, ncols)));
!       }
        MemoryContextSwitchTo(oldCxt);
  
        /* Restore former ereport callback */
        error_context_stack = syntax_errcontext.previous;
  }
  
+ /*
+  * Expression tree walker for tlist_result_column_count.  Returns true if the
+  * expression tree contains an A_Star node, false otherwise.
+  */
+ static bool
+ find_a_star_walker(Node *node, void *context)
+ {
+       if (node == NULL)
+               return false;
+       if (IsA(node, A_Star))
+               return true;
+       if (IsA(node, ColumnRef))
+       {
+               ColumnRef *ref = (ColumnRef *) node;
+               /* A_Star can only be the last element */
+               if (IsA(llast(ref->fields), A_Star))
+                       return true;
+       }
+       return raw_expression_tree_walker((Node *) node,
+                                                                         
find_a_star_walker,
+                                                                         
context);
+ }
+ 
+ /*
+  * Find the number of columns in a raw statement's targetList (if SELECT) or
+  * returningList (if INSERT, UPDATE or DELETE).  Returns -1 if the number of
+  * columns could not be determined because of an A_Star.
+  */
+ static int
+ tlist_result_column_count(Node *stmt)
+ {
+       List *tlist;
+ 
+       if (IsA(stmt, SelectStmt))
+               tlist = ((SelectStmt *) stmt)->targetList;
+       else if (IsA(stmt, InsertStmt))
+               tlist = ((InsertStmt *) stmt)->returningList;
+       else if (IsA(stmt, UpdateStmt))
+               tlist = ((UpdateStmt *) stmt)->returningList;
+       else if (IsA(stmt, DeleteStmt))
+               tlist = ((DeleteStmt *) stmt)->returningList;
+       else
+               elog(ERROR, "unknown nodeTag %d", nodeTag(stmt));
+ 
+       if (tlist == NIL)
+               return 0;
+ 
+       if (raw_expression_tree_walker((Node *) tlist, find_a_star_walker, 
NULL))
+               return -1;
+       return list_length(tlist);
+ }
+ 
  static void
  plpgsql_sql_error_callback(void *arg)
  {
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
***************
*** 87,92 **** plpgsql_extra_checks_check_hook(char **newvalue, void **extra, 
GucSource source)
--- 87,94 ----
  
                        if (pg_strcasecmp(tok, "shadowed_variables") == 0)
                                extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
+                       else if (pg_strcasecmp(tok, "num_into_expressions") == 
0)
+                               extrachecks |= 
PLPGSQL_XCHECK_NUM_INTO_EXPRESSIONS;
                        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);
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 886,894 **** 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;
--- 886,895 ----
  extern bool plpgsql_print_strict_params;
  
  /* extra compile-time checks */
! #define PLPGSQL_XCHECK_NONE                                           0
! #define PLPGSQL_XCHECK_SHADOWVAR                              1
! #define PLPGSQL_XCHECK_NUM_INTO_EXPRESSIONS           2
! #define PLPGSQL_XCHECK_ALL                                            ((int) 
~0)
  
  extern int    plpgsql_extra_warnings;
  extern int    plpgsql_extra_errors;
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 5340,5342 **** NOTICE:  outer_func() done
--- 5340,5388 ----
  drop function outer_outer_func(int);
  drop function outer_func(int);
  drop function inner_func(int);
+ create temporary table somecols(a int, b int);
+ set plpgsql.extra_warnings to 'num_into_expressions';
+ -- INTO column counts
+ create or replace function into_counts()
+ returns void as $$
+ declare
+   myresult int;
+   myrec record;
+   mycols somecols;
+ begin
+   -- no warning
+   select 1 into myresult;
+   -- we don't know the column count without parse analysis, no warning
+   select * into myresult from somecols;
+   -- records are OK
+   select 1, 2 into myrec;
+   -- warning
+   select a, b into myresult from somecols;
+   -- warning
+   select into myresult from somecols;
+   -- no warning
+   select 1, 2 into mycols;
+   -- warning
+   select 1 into mycols;
+   -- warning
+   select 1, 2, 3 into myresult;
+ end;
+ $$ language plpgsql;
+ WARNING:  the number of result expressions does not match that expected by 
the INTO target
+ LINE 15:   select a, b into myresult from somecols;
+            ^
+ DETAIL:  INTO target expects 1 expressions, but the query returns 2
+ WARNING:  the number of result expressions does not match that expected by 
the INTO target
+ LINE 17:   select into myresult from somecols;
+            ^
+ DETAIL:  INTO target expects 1 expressions, but the query returns 0
+ WARNING:  the number of result expressions does not match that expected by 
the INTO target
+ LINE 21:   select 1 into mycols;
+            ^
+ DETAIL:  INTO target expects 2 expressions, but the query returns 1
+ WARNING:  the number of result expressions does not match that expected by 
the INTO target
+ LINE 23:   select 1, 2, 3 into myresult;
+            ^
+ DETAIL:  INTO target expects 1 expressions, but the query returns 3
+ drop table somecols;
+ reset plpgsql.extra_warnings;
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 4193,4195 **** select outer_outer_func(20);
--- 4193,4230 ----
  drop function outer_outer_func(int);
  drop function outer_func(int);
  drop function inner_func(int);
+ 
+ create temporary table somecols(a int, b int);
+ 
+ set plpgsql.extra_warnings to 'num_into_expressions';
+ 
+ -- INTO column counts
+ create or replace function into_counts()
+ returns void as $$
+ declare
+   myresult int;
+   myrec record;
+   mycols somecols;
+ begin
+   -- no warning
+   select 1 into myresult;
+   -- we don't know the column count without parse analysis, no warning
+   select * into myresult from somecols;
+   -- records are OK
+   select 1, 2 into myrec;
+   -- warning
+   select a, b into myresult from somecols;
+   -- warning
+   select into myresult from somecols;
+   -- no warning
+   select 1, 2 into mycols;
+   -- warning
+   select 1 into mycols;
+   -- warning
+   select 1, 2, 3 into mycols;
+ end;
+ $$ language plpgsql;
+ 
+ drop table somecols;
+ 
+ reset plpgsql.extra_warnings;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to