Allow empty target list in SELECT.
authorTom Lane
Sun, 15 Dec 2013 01:23:26 +0000 (20:23 -0500)
committerTom Lane
Sun, 15 Dec 2013 01:23:26 +0000 (20:23 -0500)
This fixes a problem noted as a followup to bug #8648: if a query has a
semantically-empty target list, e.g. SELECT * FROM zero_column_table,
ruleutils.c will dump it as a syntactically-empty target list, which was
not allowed.  There doesn't seem to be any reliable way to fix this by
hacking ruleutils (note in particular that the originally zero-column table
might since have had columns added to it); and even if we had such a fix,
it would do nothing for existing dump files that might contain bad syntax.
The best bet seems to be to relax the syntactic restriction.

Also, add parse-analysis errors for SELECT DISTINCT with no columns (after
*-expansion) and RETURNING with no columns.  These cases previously
produced unexpected behavior because the parsed Query looked like it had
no DISTINCT or RETURNING clause, respectively.  If anyone ever offers
a plausible use-case for this, we could work a bit harder on making the
situation distinguishable.

Arguably this is a bug fix that should be back-patched, but I'm worried
that there may be client apps or PLs that expect "SELECT ;" to throw a
syntax error.  The issue doesn't seem important enough to risk changing
behavior in minor releases.

doc/src/sgml/ref/select.sgml
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/parser/parse_clause.c
src/test/regress/expected/errors.out
src/test/regress/sql/errors.sql

index d6a17cc7a443be7d189fa9fe315f62286fa78202..f9f83f34f70d90f503c16f7d2256a9ceabd8d6f1 100644 (file)
@@ -34,7 +34,7 @@ PostgreSQL documentation
 
 [ WITH [ RECURSIVE ] with_query [, ...] ]
 SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
-    * | expression [ [ AS ] output_name ] [, ...]
+    [ * | expression [ [ AS ] output_name ] [, ...] ]
     [ FROM from_item [, ...] ]
     [ WHERE condition ]
     [ GROUP BY expression [, ...] ]
@@ -1740,13 +1740,27 @@ SELECT 2+2;
     following query is invalid:
 
 SELECT distributors.* WHERE distributors.name = 'Westward';
-PostgreSQL releases prior to
+
+    PostgreSQL releases prior to
     8.1 would accept queries of this form, and add an implicit entry
     to the query's FROM clause for each table
     referenced by the query. This is no longer allowed.
    
   
 
+  
+   Empty <literal>SELECT</literal> Lists
+
+   
+    The list of output expressions after SELECT can be
+    empty, producing a zero-column result table.
+    This is not valid syntax according to the SQL standard.
+    PostgreSQL allows it to be consistent with
+    allowing zero-column tables.
+    However, an empty list is not allowed when DISTINCT is used.
+   
+  
+
   
    Omitting the <literal>AS</literal> Key Word
 
@@ -1809,10 +1823,6 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
     PostgreSQL treats UNNEST() the
     same as other set-returning functions.
    
-
-   
-    ROWS FROM( ... ) is an extension of the SQL standard.
-   
   
 
   
@@ -1910,9 +1920,13 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
    Nonstandard Clauses
 
    
-    The clause DISTINCT ON is not defined in the
+    DISTINCT ON ( ... ) is an extension of the
     SQL standard.
    
+
+   
+    ROWS FROM( ... ) is an extension of the SQL standard.
+   
   
  
 
index a9d1fecff5cf4b2b7aef3b5353448f25e35a1e94..60cce3784534c6362f82fd45334cbf820b214248 100644 (file)
@@ -2018,6 +2018,19 @@ transformReturningList(ParseState *pstate, List *returningList)
    /* transform RETURNING identically to a SELECT targetlist */
    rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING);
 
+   /*
+    * Complain if the nonempty tlist expanded to nothing (which is possible
+    * if it contains only a star-expansion of a zero-column table).  If we
+    * allow this, the parsed Query will look like it didn't have RETURNING,
+    * with results that would probably surprise the user.
+    */
+   if (rlist == NIL)
+       ereport(ERROR,
+               (errcode(ERRCODE_SYNTAX_ERROR),
+                errmsg("RETURNING must have at least one column"),
+                parser_errposition(pstate,
+                                   exprLocation(linitial(returningList)))));
+
    /* mark column origins */
    markTargetListOrigins(pstate, rlist);
 
index 8fced4427b167406e1161df6e70a0395aaa4027d..f9d45777ca7dfcfff2e2b892dcc31d723cb57e8f 100644 (file)
@@ -334,7 +334,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
                name_list from_clause from_list opt_array_bounds
                qualified_name_list any_name any_name_list
                any_operator expr_list attrs
-               target_list insert_column_list set_target_list
+               target_list opt_target_list insert_column_list set_target_list
                set_clause_list set_clause multiple_set_clause
                ctext_expr_list ctext_row def_list indirection opt_indirection
                reloption_list group_clause TriggerFuncArgs select_limit
@@ -9259,7 +9259,7 @@ select_clause:
  * However, this is not checked by the grammar; parse analysis must check it.
  */
 simple_select:
-           SELECT opt_distinct target_list
+           SELECT opt_distinct opt_target_list
            into_clause from_clause where_clause
            group_clause having_clause window_clause
                {
@@ -12215,6 +12215,10 @@ ctext_row: '(' ctext_expr_list ')'                 { $$ = $2; }
  *
  *****************************************************************************/
 
+opt_target_list: target_list                       { $$ = $1; }
+           | /* EMPTY */                           { $$ = NIL; }
+       ;
+
 target_list:
            target_el                               { $$ = list_make1($1); }
            | target_list ',' target_el             { $$ = lappend($1, $3); }
index 939fa834e0a011b79bb778667873f259e6acf37f..87b0c8fd4184b8b859d1f44b8ebc4a9ef0eef3d6 100644 (file)
@@ -2011,6 +2011,20 @@ transformDistinctClause(ParseState *pstate,
                                      true);
    }
 
+   /*
+    * Complain if we found nothing to make DISTINCT.  Returning an empty list
+    * would cause the parsed Query to look like it didn't have DISTINCT, with
+    * results that would probably surprise the user.  Note: this case is
+    * presently impossible for aggregates because of grammar restrictions,
+    * but we check anyway.
+    */
+   if (result == NIL)
+       ereport(ERROR,
+               (errcode(ERRCODE_SYNTAX_ERROR),
+                is_agg ?
+                errmsg("an aggregate with DISTINCT must have at least one argument") :
+                errmsg("SELECT DISTINCT must have at least one column")));
+
    return result;
 }
 
@@ -2115,6 +2129,11 @@ transformDistinctOnClause(ParseState *pstate, List *distinctlist,
                                      true);
    }
 
+   /*
+    * An empty result list is impossible here because of grammar restrictions.
+    */
+   Assert(result != NIL);
+
    return result;
 }
 
index 4061512977845bfce2150c46e9d954365f0ee404..5f8868da26ed33062d63c584f8aa9b0ec73a492c 100644 (file)
@@ -15,26 +15,24 @@ select 1;
 --
 --
 -- SELECT
--- missing relation name
+-- this used to be a syntax error, but now we allow an empty target list
 select;
-ERROR:  syntax error at or near ";"
-LINE 1: select;
-              ^
+--
+(1 row)
+
 -- no such relation
 select * from nonesuch;
 ERROR:  relation "nonesuch" does not exist
 LINE 1: select * from nonesuch;
                       ^
--- missing target list
-select from pg_database;
-ERROR:  syntax error at or near "from"
-LINE 1: select from pg_database;
-               ^
 -- bad name in target list
 select nonesuch from pg_database;
 ERROR:  column "nonesuch" does not exist
 LINE 1: select nonesuch from pg_database;
                ^
+-- empty distinct list isn't OK
+select distinct from pg_database;
+ERROR:  SELECT DISTINCT must have at least one column
 -- bad attribute name on lhs of operator
 select * from pg_database where nonesuch = pg_database.datname;
 ERROR:  column "nonesuch" does not exist
@@ -45,12 +43,7 @@ select * from pg_database where pg_database.datname = nonesuch;
 ERROR:  column "nonesuch" does not exist
 LINE 1: ...ect * from pg_database where pg_database.datname = nonesuch;
                                                               ^
--- bad select distinct on syntax, distinct attribute missing
-select distinct on (foobar) from pg_database;
-ERROR:  syntax error at or near "from"
-LINE 1: select distinct on (foobar) from pg_database;
-                                    ^
--- bad select distinct on syntax, distinct attribute not in target list
+-- bad attribute name in select distinct on
 select distinct on (foobar) * from pg_database;
 ERROR:  column "foobar" does not exist
 LINE 1: select distinct on (foobar) * from pg_database;
index 2ee707c5c75aab6a86762b2915fcdce4b92125e0..cd370b4781e09d9f6ab66272117e1fed99742de2 100644 (file)
@@ -16,28 +16,25 @@ select 1;
 --
 -- SELECT
 
--- missing relation name
+-- this used to be a syntax error, but now we allow an empty target list
 select;
 
 -- no such relation
 select * from nonesuch;
 
--- missing target list
-select from pg_database;
 -- bad name in target list
 select nonesuch from pg_database;
+
+-- empty distinct list isn't OK
+select distinct from pg_database;
+
 -- bad attribute name on lhs of operator
 select * from pg_database where nonesuch = pg_database.datname;
 
 -- bad attribute name on rhs of operator
 select * from pg_database where pg_database.datname = nonesuch;
 
-
--- bad select distinct on syntax, distinct attribute missing
-select distinct on (foobar) from pg_database;
-
-
--- bad select distinct on syntax, distinct attribute not in target list
+-- bad attribute name in select distinct on
 select distinct on (foobar) * from pg_database;