diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml index d6a17cc7a4..f9f83f34f7 100644 --- a/doc/src/sgml/ref/select.sgml +++ b/doc/src/sgml/ref/select.sgml @@ -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. + diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index a9d1fecff5..60cce37845 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -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); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8fced4427b..f9d45777ca 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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); } diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 939fa834e0..87b0c8fd41 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -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; } diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out index 4061512977..5f8868da26 100644 --- a/src/test/regress/expected/errors.out +++ b/src/test/regress/expected/errors.out @@ -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; diff --git a/src/test/regress/sql/errors.sql b/src/test/regress/sql/errors.sql index 2ee707c5c7..cd370b4781 100644 --- a/src/test/regress/sql/errors.sql +++ b/src/test/regress/sql/errors.sql @@ -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;