Skip to content

Commit 7cd9765

Browse files
committed
Re-allow DISTINCT in pl/pgsql expressions.
I'd omitted this from the grammar in commit c9d5298, figuring that it wasn't worth supporting. However we already have one complaint, so it seems that judgment was wrong. It doesn't require a huge amount of code, so add it back. (I'm still drawing the line at UNION/INTERSECT/EXCEPT though: those'd require an unreasonable amount of grammar refactoring, and the single-result-row restriction makes them near useless anyway.) Also rethink the documentation: this behavior is a property of all pl/pgsql expressions, not just assignments. Discussion: https://postgr.es/m/20210122134106.e94c5cd7@mail.verfriemelt.org
1 parent ab66645 commit 7cd9765

File tree

3 files changed

+70
-35
lines changed

3 files changed

+70
-35
lines changed

doc/src/sgml/plpgsql.sgml

+18-14
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,24 @@ PREPARE <replaceable>statement_name</replaceable>(integer, integer) AS SELECT $1
917917
they are useful to know when trying to diagnose a problem.
918918
More information appears in <xref linkend="plpgsql-plan-caching"/>.
919919
</para>
920+
921+
<para>
922+
Since an <replaceable>expression</replaceable> is converted to a
923+
<literal>SELECT</literal> command, it can contain the same clauses
924+
that an ordinary <literal>SELECT</literal> would, except that it
925+
cannot include a top-level <literal>UNION</literal>,
926+
<literal>INTERSECT</literal>, or <literal>EXCEPT</literal> clause.
927+
Thus for example one could test whether a table is non-empty with
928+
<programlisting>
929+
IF count(*) &gt; 0 FROM my_table THEN ...
930+
</programlisting>
931+
since the <replaceable>expression</replaceable>
932+
between <literal>IF</literal> and <literal>THEN</literal> is parsed as
933+
though it were <literal>SELECT count(*) &gt; 0 FROM my_table</literal>.
934+
The <literal>SELECT</literal> must produce a single column, and not
935+
more than one row. (If it produces no rows, the result is taken as
936+
NULL.)
937+
</para>
920938
</sect1>
921939

922940
<sect1 id="plpgsql-statements">
@@ -973,20 +991,6 @@ my_array[1:3] := array[1,2,3];
973991
complex_array[n].realpart = 12.3;
974992
</programlisting>
975993
</para>
976-
977-
<para>
978-
It's useful to know that what follows the assignment operator is
979-
essentially treated as a <literal>SELECT</literal> command; as long
980-
as it returns a single row and column, it will work. Thus for example
981-
one can write something like
982-
<programlisting>
983-
total_sales := sum(quantity) from sales;
984-
</programlisting>
985-
This provides an effect similar to the single-row <literal>SELECT
986-
... INTO</literal> syntax described in
987-
<xref linkend="plpgsql-statements-sql-onerow"/>. However, that syntax
988-
is more portable.
989-
</para>
990994
</sect2>
991995

992996
<sect2 id="plpgsql-statements-sql-noresult">

src/backend/parser/analyze.c

+24-5
Original file line numberDiff line numberDiff line change
@@ -2466,7 +2466,7 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
24662466

24672467
/*
24682468
* The rest mostly matches transformSelectStmt, except that we needn't
2469-
* consider WITH or DISTINCT, and we build a targetlist our own way.
2469+
* consider WITH or INTO, and we build a targetlist our own way.
24702470
*/
24712471
qry->commandType = CMD_SELECT;
24722472
pstate->p_is_insert = false;
@@ -2590,10 +2590,29 @@ transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
25902590
EXPR_KIND_GROUP_BY,
25912591
false /* allow SQL92 rules */ );
25922592

2593-
/* No DISTINCT clause */
2594-
Assert(!sstmt->distinctClause);
2595-
qry->distinctClause = NIL;
2596-
qry->hasDistinctOn = false;
2593+
if (sstmt->distinctClause == NIL)
2594+
{
2595+
qry->distinctClause = NIL;
2596+
qry->hasDistinctOn = false;
2597+
}
2598+
else if (linitial(sstmt->distinctClause) == NULL)
2599+
{
2600+
/* We had SELECT DISTINCT */
2601+
qry->distinctClause = transformDistinctClause(pstate,
2602+
&qry->targetList,
2603+
qry->sortClause,
2604+
false);
2605+
qry->hasDistinctOn = false;
2606+
}
2607+
else
2608+
{
2609+
/* We had SELECT DISTINCT ON */
2610+
qry->distinctClause = transformDistinctOnClause(pstate,
2611+
sstmt->distinctClause,
2612+
&qry->targetList,
2613+
qry->sortClause);
2614+
qry->hasDistinctOn = true;
2615+
}
25972616

25982617
/* transform LIMIT */
25992618
qry->limitOffset = transformLimitClause(pstate, sstmt->limitOffset,

src/backend/parser/gram.y

+28-16
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
389389
OptTableElementList TableElementList OptInherit definition
390390
OptTypedTableElementList TypedTableElementList
391391
reloptions opt_reloptions
392-
OptWith distinct_clause opt_definition func_args func_args_list
392+
OptWith opt_definition func_args func_args_list
393393
func_args_with_defaults func_args_with_defaults_list
394394
aggr_args aggr_args_list
395395
func_as createfunc_opt_list alterfunc_opt_list
@@ -401,6 +401,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
401401
name_list role_list from_clause from_list opt_array_bounds
402402
qualified_name_list any_name any_name_list type_name_list
403403
any_operator expr_list attrs
404+
distinct_clause opt_distinct_clause
404405
target_list opt_target_list insert_column_list set_target_list
405406
set_clause_list set_clause
406407
def_list operator_def_list indirection opt_indirection
@@ -11260,6 +11261,11 @@ select_clause:
1126011261
* As with select_no_parens, simple_select cannot have outer parentheses,
1126111262
* but can have parenthesized subclauses.
1126211263
*
11264+
* It might appear that we could fold the first two alternatives into one
11265+
* by using opt_distinct_clause. However, that causes a shift/reduce conflict
11266+
* against INSERT ... SELECT ... ON CONFLICT. We avoid the ambiguity by
11267+
* requiring SELECT DISTINCT [ON] to be followed by a non-empty target_list.
11268+
*
1126311269
* Note that sort clauses cannot be included at this level --- SQL requires
1126411270
* SELECT foo UNION SELECT bar ORDER BY baz
1126511271
* to be parsed as
@@ -11497,8 +11503,13 @@ opt_all_clause:
1149711503
| /*EMPTY*/
1149811504
;
1149911505

11506+
opt_distinct_clause:
11507+
distinct_clause { $$ = $1; }
11508+
| opt_all_clause { $$ = NIL; }
11509+
;
11510+
1150011511
opt_sort_clause:
11501-
sort_clause { $$ = $1;}
11512+
sort_clause { $$ = $1; }
1150211513
| /*EMPTY*/ { $$ = NIL; }
1150311514
;
1150411515

@@ -15065,32 +15076,33 @@ role_list: RoleSpec
1506515076
* Therefore the returned struct is a SelectStmt.
1506615077
*****************************************************************************/
1506715078

15068-
PLpgSQL_Expr: opt_target_list
15079+
PLpgSQL_Expr: opt_distinct_clause opt_target_list
1506915080
from_clause where_clause
1507015081
group_clause having_clause window_clause
1507115082
opt_sort_clause opt_select_limit opt_for_locking_clause
1507215083
{
1507315084
SelectStmt *n = makeNode(SelectStmt);
1507415085

15075-
n->targetList = $1;
15076-
n->fromClause = $2;
15077-
n->whereClause = $3;
15078-
n->groupClause = $4;
15079-
n->havingClause = $5;
15080-
n->windowClause = $6;
15081-
n->sortClause = $7;
15082-
if ($8)
15086+
n->distinctClause = $1;
15087+
n->targetList = $2;
15088+
n->fromClause = $3;
15089+
n->whereClause = $4;
15090+
n->groupClause = $5;
15091+
n->havingClause = $6;
15092+
n->windowClause = $7;
15093+
n->sortClause = $8;
15094+
if ($9)
1508315095
{
15084-
n->limitOffset = $8->limitOffset;
15085-
n->limitCount = $8->limitCount;
15096+
n->limitOffset = $9->limitOffset;
15097+
n->limitCount = $9->limitCount;
1508615098
if (!n->sortClause &&
15087-
$8->limitOption == LIMIT_OPTION_WITH_TIES)
15099+
$9->limitOption == LIMIT_OPTION_WITH_TIES)
1508815100
ereport(ERROR,
1508915101
(errcode(ERRCODE_SYNTAX_ERROR),
1509015102
errmsg("WITH TIES cannot be specified without ORDER BY clause")));
15091-
n->limitOption = $8->limitOption;
15103+
n->limitOption = $9->limitOption;
1509215104
}
15093-
n->lockingClause = $9;
15105+
n->lockingClause = $10;
1509415106
$$ = (Node *) n;
1509515107
}
1509615108
;

0 commit comments

Comments
 (0)