pg.ddx.io  pgsql-bugs@postgresql.org mailing list archive  
help / color / mirror / Atom feed
Missing semicolumn in anonymous plpgsql block does not raise syntax error
13+ messages / 5 participants
[nested] [flat]

* Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-02 11:24  Mor Lehr <mor.lehr@deel.com>
  0 siblings, 1 reply; 13+ messages in thread

From: Mor Lehr @ 2024-06-02 11:24 UTC (permalink / raw)
  To: pgsql-bugs@lists.postgresql.org

Hi,

I would like to report a potential bug in postgres 15.4, also reproduced on
15.6.

*The exact sequence of steps:*
Connect to a postgres 15.4 database and run the following statements:

CREATE TABLE foo3(id serial PRIMARY key, txt text);

INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');

DO $$

DECLARE

l_cnt int;

BEGIN

l_cnt := 1

DELETE FROM foo3 WHERE id=1;

END; $$;


*The output you got:*

1. The script passes (no error message) even though there's a missing
semicolon (;) after "l_cnt := 1"
2. The script doesn't actually delete the record from foo3


This caused us a production issue where we thought changes were applied
(script passed successfully) but changes weren't actually applied.


If I move the line "l_cnt := 1" to after the DELETE statement like so:

DO $$

DECLARE

l_cnt int;

BEGIN

DELETE FROM foo3 WHERE id=1;

l_cnt := 1

END; $$;

I get the error - as expected:

SQL Error [42601]: ERROR: syntax error at end of input
  Position: 89


Furthermore, replacing the DELETE statement with an UPDATE statement in the
original code does raise an error:

DO $$

DECLARE

l_cnt int;

BEGIN

l_cnt := 1

UPDATE foo3 SET txt='ccc' WHERE id=1;

END; $$;

SQL Error [42601]: ERROR: syntax error at or near "foo3"
  Position: 62

But adding the semicolon - it works correctly with either UPDATE or DELETE.


I ran the original code using the following clients to make sure it's not a
client problem:

1. psql

2. DBeaver using standard JDBC drivers

3. Flyway using JDBC drivers




*Versions:*

PostgreSQL 15.6 (Homebrew) on x86_64-apple-darwin23.2.0, compiled by Apple
clang version 15.0.0 (clang-1500.1.0.2.5), 64-bit l - running locally on my
MacBook


PostgreSQL 15.4 on aarch64-unknown-linux-gnu, compiled by
aarch64-unknown-linux-gnu-gcc (GCC) 9.5.0, 64-bit - running on AWS RDS
Aurora


*Installed Extensions (On AWS RDS):*

 oid       |extname
 |extowner|extnamespace|extrelocatable|extversion|extconfig   |extcondition|
----------+-------------------------+--------+------------+--------------+----------+------------+------------+
     16463|btree_gist               |      10|        2200|true
 |1.7       |NULL        |NULL        |
1463651797|deel_password_check_rules|   16399|        2200|false
|1.0       |NULL        |NULL        |
     16464|fuzzystrmatch            |      10|        2200|true
 |1.1       |NULL        |NULL        |
 958297705|pg_repack                |      10|        2200|false
|1.4.8     |NULL        |NULL        |
     16465|pg_stat_statements       |      10|        2200|true
 |1.9       |NULL        |NULL        |
1463506085|pg_tle                   |      10|  1463506084|false
|1.1.1     |{1463506117}|{""}        |
     16467|pg_trgm                  |      10|        2200|true
 |1.6       |NULL        |NULL        |
     16468|pgcrypto                 |      10|        2200|true
 |1.3       |NULL        |NULL        |
     14498|plpgsql                  |      10|          11|false
|1.0       |NULL        |NULL        |
     16469|postgres_fdw             |      10|        2200|true
 |1.1       |NULL        |NULL        |
     16470|tablefunc                |      10|        2200|true
 |1.0       |NULL        |NULL        |
     16471|unaccent                 |      10|        2200|true
 |1.1       |NULL        |NULL        |
     16472|uuid-ossp                |      10|        2200|true
 |1.1       |NULL        |NULL        |


Please let me know what other information I can provide.


Thanks,

Mor

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-02 15:19  David G. Johnston <david.g.johnston@gmail.com>
  parent: Mor Lehr <mor.lehr@deel.com>
  0 siblings, 2 replies; 13+ messages in thread

From: David G. Johnston @ 2024-06-02 15:19 UTC (permalink / raw)
  To: Mor Lehr <mor.lehr@deel.com>; +Cc: pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

On Sunday, June 2, 2024, Mor Lehr <mor.lehr@deel.com> wrote:

> Hi,
>
> I would like to report a potential bug in postgres 15.4, also reproduced
> on 15.6.
>
> *The exact sequence of steps:*
> Connect to a postgres 15.4 database and run the following statements:
>
> CREATE TABLE foo3(id serial PRIMARY key, txt text);
>
> INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');
>
> DO $$
>
> DECLARE
>
> l_cnt int;
>
> BEGIN
>
> l_cnt := 1
>
> DELETE FROM foo3 WHERE id=1;
>
> END; $$;
>
>
> *The output you got:*
>
> 1. The script passes (no error message) even though there's a missing
> semicolon (;) after "l_cnt := 1"
> 2. The script doesn't actually delete the record from foo3
>
>
>
I think you just wrote the equivalent of:

l_cnt := (select 1 as delete from foo3 where id=1);

Which is a valid query.

David J.

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-02 15:26  Mor Lehr <mor.lehr@deel.com>
  parent: David G. Johnston <david.g.johnston@gmail.com>
  1 sibling, 1 reply; 13+ messages in thread

From: Mor Lehr @ 2024-06-02 15:26 UTC (permalink / raw)
  To: David G. Johnston <david.g.johnston@gmail.com>; +Cc: pgsql-bugs@lists.postgresql.org

Thanks for the prompt reply.
Can you please refer me to the section in the documentation that describes
this behavior?
This (automatically interperting 1 as select 1) is totally an unexpected
behavior for me.

Thanks again.
-Mor.


On Sun, Jun 2, 2024, 18:19 David G. Johnston <david.g.johnston@gmail.com>
wrote:

> On Sunday, June 2, 2024, Mor Lehr <mor.lehr@deel.com> wrote:
>
>> Hi,
>>
>> I would like to report a potential bug in postgres 15.4, also reproduced
>> on 15.6.
>>
>> *The exact sequence of steps:*
>> Connect to a postgres 15.4 database and run the following statements:
>>
>> CREATE TABLE foo3(id serial PRIMARY key, txt text);
>>
>> INSERT INTO foo3 (txt) VALUES ('aaa'),('bbb');
>>
>> DO $$
>>
>> DECLARE
>>
>> l_cnt int;
>>
>> BEGIN
>>
>> l_cnt := 1
>>
>> DELETE FROM foo3 WHERE id=1;
>>
>> END; $$;
>>
>>
>> *The output you got:*
>>
>> 1. The script passes (no error message) even though there's a missing
>> semicolon (;) after "l_cnt := 1"
>> 2. The script doesn't actually delete the record from foo3
>>
>>
>>
> I think you just wrote the equivalent of:
>
> l_cnt := (select 1 as delete from foo3 where id=1);
>
> Which is a valid query.
>
> David J.
>
>
>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-02 15:31  David G. Johnston <david.g.johnston@gmail.com>
  parent: Mor Lehr <mor.lehr@deel.com>
  0 siblings, 1 reply; 13+ messages in thread

From: David G. Johnston @ 2024-06-02 15:31 UTC (permalink / raw)
  To: Mor Lehr <mor.lehr@deel.com>; +Cc: pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

On Sunday, June 2, 2024, Mor Lehr <mor.lehr@deel.com> wrote:

> Thanks for the prompt reply.
> Can you please refer me to the section in the documentation that describes
> this behavior?
> This (automatically interperting 1 as select 1) is totally an unexpected
> behavior for me.
>


https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-ASSIGNMENT

“As explained previously, the expression in such a statement is evaluated
by means of an SQL SELECT command sent to the main database engine.”

David J.

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-02 15:42  Mor Lehr <mor.lehr@deel.com>
  parent: David G. Johnston <david.g.johnston@gmail.com>
  0 siblings, 1 reply; 13+ messages in thread

From: Mor Lehr @ 2024-06-02 15:42 UTC (permalink / raw)
  To: David G. Johnston <david.g.johnston@gmail.com>; +Cc: pgsql-bugs@lists.postgresql.org

Thanks for the reference.
We learn new stuff every day.

You can close the case.
Thanks, Mor


On Sun, Jun 2, 2024, 18:31 David G. Johnston <david.g.johnston@gmail.com>
wrote:

> On Sunday, June 2, 2024, Mor Lehr <mor.lehr@deel.com> wrote:
>
>> Thanks for the prompt reply.
>> Can you please refer me to the section in the documentation that
>> describes this behavior?
>> This (automatically interperting 1 as select 1) is totally an unexpected
>> behavior for me.
>>
>
>
> https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-ASSIGNMENT
>
> “As explained previously, the expression in such a statement is evaluated
> by means of an SQL SELECT command sent to the main database engine.”
>
> David J.
>
>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-02 22:18  Tom Lane <tgl@sss.pgh.pa.us>
  parent: David G. Johnston <david.g.johnston@gmail.com>
  1 sibling, 1 reply; 13+ messages in thread

From: Tom Lane @ 2024-06-02 22:18 UTC (permalink / raw)
  To: David G. Johnston <david.g.johnston@gmail.com>; +Cc: Mor Lehr <mor.lehr@deel.com>; pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

"David G. Johnston" <david.g.johnston@gmail.com> writes:
> I think you just wrote the equivalent of:
> l_cnt := (select 1 as delete from foo3 where id=1);
> Which is a valid query.

Still another example of the folly of letting AS be optional.
I don't suppose we can ever undo that though.

			regards, tom lane





^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-03 16:45  Erik Wienhold <ewie@ewie.name>
  parent: Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 13+ messages in thread

From: Erik Wienhold @ 2024-06-03 16:45 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: David G. Johnston <david.g.johnston@gmail.com>; Mor Lehr <mor.lehr@deel.com>; pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

On 2024-06-03 00:18 +0200, Tom Lane wrote:
> "David G. Johnston" <david.g.johnston@gmail.com> writes:
> > I think you just wrote the equivalent of:
> > l_cnt := (select 1 as delete from foo3 where id=1);
> > Which is a valid query.
> 
> Still another example of the folly of letting AS be optional.
> I don't suppose we can ever undo that though.

How about inventing an opt-in strict mode (like in Perl or JavaScript)
that prevents certain footguns?  For example, disallowing bare column
labels.

That could be enabled for the current session or transaction:

    SET strict_parsing = { on | off };

Or just for individual routines:

    CREATE PROCEDURE myproc()
        SET strict_parsing = { on | off }
        LANGUAGE plpgsql ...

-- 
Erik





^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-03 18:11  Pavel Stehule <pavel.stehule@gmail.com>
  parent: Erik Wienhold <ewie@ewie.name>
  0 siblings, 1 reply; 13+ messages in thread

From: Pavel Stehule @ 2024-06-03 18:11 UTC (permalink / raw)
  To: Erik Wienhold <ewie@ewie.name>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; David G. Johnston <david.g.johnston@gmail.com>; Mor Lehr <mor.lehr@deel.com>; pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

po 3. 6. 2024 v 18:46 odesílatel Erik Wienhold <ewie@ewie.name> napsal:

> On 2024-06-03 00:18 +0200, Tom Lane wrote:
> > "David G. Johnston" <david.g.johnston@gmail.com> writes:
> > > I think you just wrote the equivalent of:
> > > l_cnt := (select 1 as delete from foo3 where id=1);
> > > Which is a valid query.
> >
> > Still another example of the folly of letting AS be optional.
> > I don't suppose we can ever undo that though.
>
> How about inventing an opt-in strict mode (like in Perl or JavaScript)
> that prevents certain footguns?  For example, disallowing bare column
> labels.
>
> That could be enabled for the current session or transaction:
>
>     SET strict_parsing = { on | off };
>
> Or just for individual routines:
>
>     CREATE PROCEDURE myproc()
>         SET strict_parsing = { on | off }
>         LANGUAGE plpgsql ...
>
>
Probably it is not bad idea - it can be generally useful

But I think it is better to introduce a new entry for plpgsql expressions
in gram.y.

Unfortunately it is not a compatible change. Years ago was popular to use a
pattern

a := tab.a FROM tab

instead correct

a := (SELECT tab.a FROM tab)

or

SELECT tab.a FROM tab INTO a;

Regards

Pavel



> --
> Erik
>
>
>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-04 06:39  Mor Lehr <mor.lehr@deel.com>
  parent: Pavel Stehule <pavel.stehule@gmail.com>
  0 siblings, 1 reply; 13+ messages in thread

From: Mor Lehr @ 2024-06-04 06:39 UTC (permalink / raw)
  To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: Erik Wienhold <ewie@ewie.name>; Tom Lane <tgl@sss.pgh.pa.us>; David G. Johnston <david.g.johnston@gmail.com>; pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

>
> How about inventing an opt-in strict mode


That can be useful at the session level, because we use anonymous blocks
quite often.
I assume if such a setting existed - we would have used it in the original
scenario.

Thanks again,
-Mor

On Mon, Jun 3, 2024 at 9:12 PM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

>
>
> po 3. 6. 2024 v 18:46 odesílatel Erik Wienhold <ewie@ewie.name> napsal:
>
>> On 2024-06-03 00:18 +0200, Tom Lane wrote:
>> > "David G. Johnston" <david.g.johnston@gmail.com> writes:
>> > > I think you just wrote the equivalent of:
>> > > l_cnt := (select 1 as delete from foo3 where id=1);
>> > > Which is a valid query.
>> >
>> > Still another example of the folly of letting AS be optional.
>> > I don't suppose we can ever undo that though.
>>
>> How about inventing an opt-in strict mode (like in Perl or JavaScript)
>> that prevents certain footguns?  For example, disallowing bare column
>> labels.
>>
>> That could be enabled for the current session or transaction:
>>
>>     SET strict_parsing = { on | off };
>>
>> Or just for individual routines:
>>
>>     CREATE PROCEDURE myproc()
>>         SET strict_parsing = { on | off }
>>         LANGUAGE plpgsql ...
>>
>>
> Probably it is not bad idea - it can be generally useful
>
> But I think it is better to introduce a new entry for plpgsql expressions
> in gram.y.
>
> Unfortunately it is not a compatible change. Years ago was popular to use
> a pattern
>
> a := tab.a FROM tab
>
> instead correct
>
> a := (SELECT tab.a FROM tab)
>
> or
>
> SELECT tab.a FROM tab INTO a;
>
> Regards
>
> Pavel
>
>
>
>> --
>> Erik
>>
>>
>>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-10 11:56  Pavel Stehule <pavel.stehule@gmail.com>
  parent: Mor Lehr <mor.lehr@deel.com>
  0 siblings, 1 reply; 13+ messages in thread

From: Pavel Stehule @ 2024-06-10 11:56 UTC (permalink / raw)
  To: Mor Lehr <mor.lehr@deel.com>; +Cc: Erik Wienhold <ewie@ewie.name>; Tom Lane <tgl@sss.pgh.pa.us>; David G. Johnston <david.g.johnston@gmail.com>; pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

Hi

út 4. 6. 2024 v 8:39 odesílatel Mor Lehr <mor.lehr@deel.com> napsal:

> How about inventing an opt-in strict mode
>
>
> That can be useful at the session level, because we use anonymous blocks
> quite often.
> I assume if such a setting existed - we would have used it in the original
> scenario.
>
> Thanks again,
> -Mor
>
> On Mon, Jun 3, 2024 at 9:12 PM Pavel Stehule <pavel.stehule@gmail.com>
> wrote:
>
>>
>>
>> po 3. 6. 2024 v 18:46 odesílatel Erik Wienhold <ewie@ewie.name> napsal:
>>
>>> On 2024-06-03 00:18 +0200, Tom Lane wrote:
>>> > "David G. Johnston" <david.g.johnston@gmail.com> writes:
>>> > > I think you just wrote the equivalent of:
>>> > > l_cnt := (select 1 as delete from foo3 where id=1);
>>> > > Which is a valid query.
>>> >
>>> > Still another example of the folly of letting AS be optional.
>>> > I don't suppose we can ever undo that though.
>>>
>>> How about inventing an opt-in strict mode (like in Perl or JavaScript)
>>> that prevents certain footguns?  For example, disallowing bare column
>>> labels.
>>>
>>> That could be enabled for the current session or transaction:
>>>
>>>     SET strict_parsing = { on | off };
>>>
>>> Or just for individual routines:
>>>
>>>     CREATE PROCEDURE myproc()
>>>         SET strict_parsing = { on | off }
>>>         LANGUAGE plpgsql ...
>>>
>>>
>> Probably it is not bad idea - it can be generally useful
>>
>> But I think it is better to introduce a new entry for plpgsql expressions
>> in gram.y.
>>
>> Unfortunately it is not a compatible change. Years ago was popular to use
>> a pattern
>>
>> a := tab.a FROM tab
>>
>> instead correct
>>
>> a := (SELECT tab.a FROM tab)
>>
>> or
>>
>> SELECT tab.a FROM tab INTO a;
>>
>> Regards
>>
>> Pavel
>>
>
I wrote an experimental patch that enforces strict syntax for PL/pgSQL
expressions. This patch is a proof concept and shows impacts of the change
(check-world tests passed)

Unfortunately it introduces break compatibility - with this patch the odd
syntax (that is undocumented) is not supported. Something like

DECLARE _x int := x FROM foo WHERE id = _arg;

should not be written in strict mode;

This patch very well fix reported issue:

(2024-06-10 12:06:43) postgres=# CREATE TABLE foo3(id serial PRIMARY key,
txt text);
CREATE TABLE
(2024-06-10 12:07:13) postgres=# INSERT INTO foo3 (txt) VALUES
('aaa'),('bbb');
INSERT 0 2
(2024-06-10 12:07:33) postgres=# DO $$
DECLARE
    l_cnt int;
BEGIN
    l_cnt := 1
    DELETE FROM foo3 WHERE id=1;
END; $$;
ERROR:  syntax error at or near "DELETE"
LINE 11:     DELETE FROM foo3 WHERE id=1;
             ^

Personally, I think it can be a strong step forward (we can use deeper
integration of plpgsql with SQL parser which was not possible before).

Because it introduces a compatibility break I don't propose to use it by
default. I see two possible ways, how this check can be used:

1. we can use plpgsql.extra_errors (
https://www.postgresql.org/docs/current/plpgsql-development-tips.html#PLPGSQL-EXTRA-CHECKS
) and introduce a check named (maybe) strict_expr_syntax. Because in this
case the warning cannot be raised, then this check cannot be used in
plpgsql.extra_warnings. I think it can work nicely.

2. if @1 will not be possible, the minimalist implementation can be based
on a new public entry to SQL parser.  In this case, I can do the proposed
check at least from plpgsql_check.

Do you see any other possibilities?

This patch is just a proof concept. I didn't implement any mechanism to
switch from default mode to strict mode (I don't propose strict mode as
default) now.

I think it can increase the robustness of plpgsql, on the other hand it
introduces compatibility breaks and I understand related problems.

The change of definition of PLpgSQL_Expr has an impact on OPEN and CASE
PLpgSQL statements that use multicolumn results.

Regards

Pavel


>
>>
>>
>>> --
>>> Erik
>>>
>>>
>>>

Attachments:

plpgsql-strict-expr.patch (text/x-patch, 26.4K) download commit 108d99d96cffe5468ec95308a34f9164d8310119 Author: okbob@github.com <pavel.stehule@gmail.com> Date: Sat Jun 8 13:09:40 2024 +0200 use strict rules for parsing PL/pgSQL expressions Originaly the rule PLpgSQL_Expr allows almost all SQL clauses. It was designed to allow old undocummented syntax var := col FROM tab; The reason for support of this "strange" syntax was technical. The PLpgSQL parser cannot to use SQL parser accurately (it was really primitive), and people found this undocumented syntax. Lattery, when it was possible to do exact parsing, from compatibility reasons, the parsing of PL/pgSQL expressions allows described syntax. Unfortunately, with support almost all SQL clauses, the PLpgSQL can accept really broken code like DO $$ DECLARE l_cnt int; BEGIN l_cnt := 1 DELETE FROM foo3 WHERE id=1; END; $$; proposed patch use strictly a_expr for PLpgSQL expressions, and solve this issue. The change of definition of PLpgSQL_Expr has an impact on OPEN and CASE PLpgSQL statements that use multicolumn results. This patch has lot of impacts on regress tests - it can be zero, if the strict syntax will be enabled by using plpgsql.extra_errors instead. Now, the changes of regress tests can be interesting for visibility of impact of strict mode. diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_archive/expected/basic_archive.out index 0015053e0f..280ff3e022 100644 --- a/contrib/basic_archive/expected/basic_archive.out +++ b/contrib/basic_archive/expected/basic_archive.out @@ -11,8 +11,8 @@ DECLARE loops int := 0; BEGIN LOOP - archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a - WHERE a ~ '^[0-9A-F]{24}$'; + archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a + WHERE a ~ '^[0-9A-F]{24}$'); IF archived OR loops > 120 * 10 THEN EXIT; END IF; PERFORM pg_sleep(0.1); loops := loops + 1; diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_archive/sql/basic_archive.sql index 14e236d57a..2c127a821f 100644 --- a/contrib/basic_archive/sql/basic_archive.sql +++ b/contrib/basic_archive/sql/basic_archive.sql @@ -7,8 +7,8 @@ DECLARE loops int := 0; BEGIN LOOP - archived := count(*) > 0 FROM pg_ls_dir('.', false, false) a - WHERE a ~ '^[0-9A-F]{24}$'; + archived := (SELECT count(*) > 0 FROM pg_ls_dir('.', false, false) a + WHERE a ~ '^[0-9A-F]{24}$'); IF archived OR loops > 120 * 10 THEN EXIT; END IF; PERFORM pg_sleep(0.1); loops := loops + 1; diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index e516c0a67c..40d98da628 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -2983,6 +2983,10 @@ _SPI_error_callback(void *arg) case RAW_PARSE_PLPGSQL_ASSIGN3: errcontext("PL/pgSQL assignment \"%s\"", query); break; + case RAW_PARSE_PLPGSQL_EXPR_LIST: + case RAW_PARSE_PLPGSQL_EXPR_WITH_LABEL_LIST: + errcontext("PL/pgSQL stetement OPEN \"%s\"", query); + break; default: errcontext("SQL statement \"%s\"", query); break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 4d582950b7..e5409bb7f2 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -65,7 +65,6 @@ #include "utils/numeric.h" #include "utils/xml.h" - /* * Location tracking support --- simpler than bison's default, since we only * want to track the start position not the end position of each nonterminal. @@ -323,7 +322,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <node> select_no_parens select_with_parens select_clause simple_select values_clause - PLpgSQL_Expr PLAssignStmt + PLpgSQL_Expr PLAssignStmt PLpgSQL_Expr_list + PLpgSQL_Expr_with_label_list + +%type <target> plpgsql_unnamed_target_el +%type <list> plpgsql_unnamed_target_list %type <str> opt_single_name %type <list> opt_qualified_name @@ -439,7 +442,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); name_list role_list from_clause from_list opt_array_bounds qualified_name_list any_name any_name_list type_name_list any_operator expr_list attrs - distinct_clause opt_distinct_clause + distinct_clause target_list opt_target_list insert_column_list set_target_list merge_values_clause set_clause_list set_clause @@ -827,7 +830,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %token MODE_PLPGSQL_ASSIGN1 %token MODE_PLPGSQL_ASSIGN2 %token MODE_PLPGSQL_ASSIGN3 - +%token MODE_PLPGSQL_EXPR_LIST +%token MODE_PLPGSQL_EXPR_WITH_LABEL_LIST /* Precedence: lowest to highest */ %left UNION EXCEPT @@ -958,6 +962,16 @@ parse_toplevel: pg_yyget_extra(yyscanner)->parsetree = list_make1(makeRawStmt((Node *) n, 0)); } + | MODE_PLPGSQL_EXPR_LIST PLpgSQL_Expr_list + { + pg_yyget_extra(yyscanner)->parsetree = + list_make1(makeRawStmt($2, 0)); + } + | MODE_PLPGSQL_EXPR_WITH_LABEL_LIST PLpgSQL_Expr_with_label_list + { + pg_yyget_extra(yyscanner)->parsetree = + list_make1(makeRawStmt($2, 0)); + } ; /* @@ -13118,11 +13132,6 @@ opt_all_clause: | /*EMPTY*/ ; -opt_distinct_clause: - distinct_clause { $$ = $1; } - | opt_all_clause { $$ = NIL; } - ; - opt_sort_clause: sort_clause { $$ = $1; } | /*EMPTY*/ { $$ = NIL; } @@ -17429,7 +17438,6 @@ role_list: RoleSpec { $$ = lappend($1, $3); } ; - /***************************************************************************** * * PL/pgSQL extensions @@ -17439,34 +17447,61 @@ role_list: RoleSpec * Therefore the returned struct is a SelectStmt. *****************************************************************************/ -PLpgSQL_Expr: opt_distinct_clause opt_target_list - from_clause where_clause - group_clause having_clause window_clause - opt_sort_clause opt_select_limit opt_for_locking_clause +PLpgSQL_Expr: a_expr { SelectStmt *n = makeNode(SelectStmt); + ResTarget *rt; + + rt = makeNode(ResTarget); + rt->name = NULL; + rt->indirection = NIL; + rt->val = (Node *) $1; + rt->location = @1; + + n->targetList = list_make1((Node *) rt); + + $$ = (Node *) n; + } + ; + +plpgsql_unnamed_target_el: a_expr + { + $$ = makeNode(ResTarget); + $$->name = NULL; + $$->indirection = NIL; + $$->val = (Node *) $1; + $$->location = @1; + } + ; + +plpgsql_unnamed_target_list: + plpgsql_unnamed_target_el + { + $$ = list_make1($1); + } + | plpgsql_unnamed_target_list ',' plpgsql_unnamed_target_el + { + $$ = lappend($1, $3); + } + ; + +PLpgSQL_Expr_list: plpgsql_unnamed_target_list + { + SelectStmt *n = makeNode(SelectStmt); + + n->targetList = $1; + + $$ = (Node *) n; + + } + ; + +PLpgSQL_Expr_with_label_list: target_list + { + SelectStmt *n = makeNode(SelectStmt); + + n->targetList = $1; - n->distinctClause = $1; - n->targetList = $2; - n->fromClause = $3; - n->whereClause = $4; - n->groupClause = ($5)->list; - n->groupDistinct = ($5)->distinct; - n->havingClause = $6; - n->windowClause = $7; - n->sortClause = $8; - if ($9) - { - n->limitOffset = $9->limitOffset; - n->limitCount = $9->limitCount; - if (!n->sortClause && - $9->limitOption == LIMIT_OPTION_WITH_TIES) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("WITH TIES cannot be specified without ORDER BY clause"))); - n->limitOption = $9->limitOption; - } - n->lockingClause = $10; $$ = (Node *) n; } ; diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c index 118488c3f3..5fc94695fb 100644 --- a/src/backend/parser/parser.c +++ b/src/backend/parser/parser.c @@ -62,6 +62,8 @@ raw_parser(const char *str, RawParseMode mode) [RAW_PARSE_PLPGSQL_ASSIGN1] = MODE_PLPGSQL_ASSIGN1, [RAW_PARSE_PLPGSQL_ASSIGN2] = MODE_PLPGSQL_ASSIGN2, [RAW_PARSE_PLPGSQL_ASSIGN3] = MODE_PLPGSQL_ASSIGN3, + [RAW_PARSE_PLPGSQL_EXPR_LIST] = MODE_PLPGSQL_EXPR_LIST, + [RAW_PARSE_PLPGSQL_EXPR_WITH_LABEL_LIST] = MODE_PLPGSQL_EXPR_WITH_LABEL_LIST, }; yyextra.have_lookahead = true; diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h index be184ec506..890b9451e2 100644 --- a/src/include/parser/parser.h +++ b/src/include/parser/parser.h @@ -33,6 +33,12 @@ * RAW_PARSE_PLPGSQL_ASSIGNn: parse a PL/pgSQL assignment statement, * and return a one-element List containing a RawStmt node. "n" * gives the number of dotted names comprising the target ColumnRef. + * + * RAW_PARSE_PLPGSQL_EXPR_LIST is used by PLpgSQL cursors when we detect + * no named argument is used. + * + * RAW_PARSE_PLPGSQL_EXPR_WITH_LABEL_LIST is used for cursor's argumet + * when named arguments are used. */ typedef enum { @@ -42,6 +48,8 @@ typedef enum RAW_PARSE_PLPGSQL_ASSIGN1, RAW_PARSE_PLPGSQL_ASSIGN2, RAW_PARSE_PLPGSQL_ASSIGN3, + RAW_PARSE_PLPGSQL_EXPR_LIST, + RAW_PARSE_PLPGSQL_EXPR_WITH_LABEL_LIST } RawParseMode; /* Values for the backslash_quote GUC */ diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl index fe8d3e5178..9c96d6e017 100644 --- a/src/interfaces/ecpg/preproc/parse.pl +++ b/src/interfaces/ecpg/preproc/parse.pl @@ -89,7 +89,11 @@ my %replace_types = ( 'PLpgSQL_Expr' => 'ignore', 'PLAssignStmt' => 'ignore', 'plassign_target' => 'ignore', - 'plassign_equals' => 'ignore',); + 'plassign_equals' => 'ignore', + 'PLpgSQL_Expr_list' => 'ignore', + 'PLpgSQL_Expr_with_label_list' => 'ignore', + 'plpgsql_unnamed_target_el' => 'ignore', + 'plpgsql_unnamed_target_list' => 'ignore'); # these replace_line commands excise certain keywords from the core keyword # lists. Be sure to account for these in ColLabel and related productions. diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out index ad60e0e8be..6819c0435f 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_array.out +++ b/src/pl/plpgsql/src/expected/plpgsql_array.out @@ -50,34 +50,38 @@ do $$ declare a quadarray; begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$; NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11 do $$ declare a int[]; -begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$; +begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$; NOTICE: a = {1,2,3} create temp table onecol as select array[1,2] as f1; do $$ declare a int[]; -begin a := f1 from onecol; raise notice 'a = %', a; end$$; +begin a := (select f1 from onecol); raise notice 'a = %', a; end$$; NOTICE: a = {1,2} do $$ declare a int[]; -begin a := * from onecol for update; raise notice 'a = %', a; end$$; +begin a := (select * from onecol for update); raise notice 'a = %', a; end$$; NOTICE: a = {1,2} -- error cases: do $$ declare a int[]; -begin a := from onecol; raise notice 'a = %', a; end$$; -ERROR: assignment source returned 0 columns -CONTEXT: PL/pgSQL assignment "a := from onecol" -PL/pgSQL function inline_code_block line 2 at assignment +begin a := (select from onecol); raise notice 'a = %', a; end$$; +ERROR: subquery must return only one column +LINE 1: a := (select from onecol) + ^ +QUERY: a := (select from onecol) +CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment do $$ declare a int[]; -begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$; -ERROR: assignment source returned 2 columns -CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol" -PL/pgSQL function inline_code_block line 2 at assignment +begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$; +ERROR: subquery must return only one column +LINE 1: a := (select f1, f1 from onecol) + ^ +QUERY: a := (select f1, f1 from onecol) +CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment insert into onecol values(array[11]); do $$ declare a int[]; -begin a := f1 from onecol; raise notice 'a = %', a; end$$; -ERROR: query returned more than one row -CONTEXT: query: a := f1 from onecol +begin a := (select f1 from onecol); raise notice 'a = %', a; end$$; +ERROR: more than one row returned by a subquery used as an expression +CONTEXT: PL/pgSQL assignment "a := (select f1 from onecol)" PL/pgSQL function inline_code_block line 2 at assignment do $$ declare a int[]; -begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$; +begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$; NOTICE: a = {1,2} do $$ declare a real; begin a[1] := 2; raise notice 'a = %', a; end$$; diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index a29d2dfacd..2c36bc6800 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -70,6 +70,8 @@ static PLpgSQL_expr *read_sql_construct(int until, int *endtoken); static PLpgSQL_expr *read_sql_expression(int until, const char *expected); +static PLpgSQL_expr *read_sql_expressions(int until, + const char *expected); static PLpgSQL_expr *read_sql_expression2(int until, int until2, const char *expected, int *endtoken); @@ -176,7 +178,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %type <nsitem> decl_aliasitem %type <expr> expr_until_semi -%type <expr> expr_until_then expr_until_loop opt_expr_until_when +%type <expr> expr_until_then expr_until_loop opt_expr_until_when exprs_until_then %type <expr> opt_exitcond %type <var> cursor_variable @@ -1239,7 +1241,7 @@ case_when_list : case_when_list case_when } ; -case_when : K_WHEN expr_until_then proc_sect +case_when : K_WHEN exprs_until_then proc_sect { PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when)); @@ -1250,6 +1252,10 @@ case_when : K_WHEN expr_until_then proc_sect } ; +exprs_until_then : + { $$ = read_sql_expressions(K_THEN, "THEN"); } + ; + opt_case_else : { $$ = NIL; @@ -2641,6 +2647,15 @@ read_sql_expression(int until, const char *expected) true, true, NULL, NULL); } +/* Convenience routine to read an expression with one possible terminator */ +static PLpgSQL_expr * +read_sql_expressions(int until, const char *expected) +{ + return read_sql_construct(until, 0, 0, expected, + RAW_PARSE_PLPGSQL_EXPR_LIST, + true, true, NULL, NULL); +} + /* Convenience routine to read an expression with two possible terminators */ static PLpgSQL_expr * read_sql_expression2(int until, int until2, const char *expected, @@ -3977,7 +3992,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until) expr = palloc0(sizeof(PLpgSQL_expr)); expr->query = pstrdup(ds.data); - expr->parseMode = RAW_PARSE_PLPGSQL_EXPR; + expr->parseMode = any_named ? RAW_PARSE_PLPGSQL_EXPR_WITH_LABEL_LIST : RAW_PARSE_PLPGSQL_EXPR_LIST; expr->plan = NULL; expr->paramnos = NULL; expr->target_param = -1; @@ -4151,7 +4166,7 @@ make_case(int location, PLpgSQL_expr *t_expr, StringInfoData ds; /* We expect to have expressions not statements */ - Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR); + Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR_LIST); /* Do the string hacking */ initStringInfo(&ds); diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql index 4b9ff51594..699713696d 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_array.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql @@ -46,31 +46,31 @@ do $$ declare a quadarray; begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$; do $$ declare a int[]; -begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$; +begin a := (select array_agg(x) from (values(1),(2),(3)) v(x)); raise notice 'a = %', a; end$$; create temp table onecol as select array[1,2] as f1; do $$ declare a int[]; -begin a := f1 from onecol; raise notice 'a = %', a; end$$; +begin a := (select f1 from onecol); raise notice 'a = %', a; end$$; do $$ declare a int[]; -begin a := * from onecol for update; raise notice 'a = %', a; end$$; +begin a := (select * from onecol for update); raise notice 'a = %', a; end$$; -- error cases: do $$ declare a int[]; -begin a := from onecol; raise notice 'a = %', a; end$$; +begin a := (select from onecol); raise notice 'a = %', a; end$$; do $$ declare a int[]; -begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$; +begin a := (select f1, f1 from onecol); raise notice 'a = %', a; end$$; insert into onecol values(array[11]); do $$ declare a int[]; -begin a := f1 from onecol; raise notice 'a = %', a; end$$; +begin a := (select f1 from onecol); raise notice 'a = %', a; end$$; do $$ declare a int[]; -begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$; +begin a := (select f1 from onecol limit 1); raise notice 'a = %', a; end$$; do $$ declare a real; begin a[1] := 2; raise notice 'a = %', a; end$$; diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl index f3e27c19af..80124c084f 100644 --- a/src/test/recovery/t/026_overwrite_contrecord.pl +++ b/src/test/recovery/t/026_overwrite_contrecord.pl @@ -33,7 +33,7 @@ $node->safe_psql( 'postgres', q{ DO $$ DECLARE - wal_segsize int := setting::int FROM pg_settings WHERE name = 'wal_segment_size'; + wal_segsize int := (select setting::int FROM pg_settings WHERE name = 'wal_segment_size'); remain int; iters int := 0; BEGIN @@ -43,7 +43,7 @@ BEGIN from generate_series(1, 10) g; remain := wal_segsize - (pg_current_wal_insert_lsn() - '0/0') % wal_segsize; - IF remain < 2 * setting::int from pg_settings where name = 'block_size' THEN + IF (select remain < 2 * setting::int from pg_settings where name = 'block_size') THEN RAISE log 'exiting after % iterations, % bytes to end of WAL segment', iters, remain; EXIT; END IF; diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 673361e840..79893df484 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2627,7 +2627,7 @@ LANGUAGE plpgsql AS $$ DECLARE v_relfilenode oid; BEGIN - v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename; + v_relfilenode := (select relfilenode FROM pg_class WHERE oid = p_tablename); EXECUTE p_ddl; diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out index 4e59188196..faae99515f 100644 --- a/src/test/regress/expected/plancache.out +++ b/src/test/regress/expected/plancache.out @@ -139,7 +139,7 @@ create temp view v1 as select 2+2 as f1; create function cache_test_2() returns int as $$ begin - return f1 from v1; + return (select f1 from v1); end$$ language plpgsql; select cache_test_2(); cache_test_2 diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 074af8f33a..f2eaac9d69 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -121,7 +121,7 @@ create trigger tg_room_ad after delete -- ************************************************************ create function tg_wslot_biu() returns trigger as $$ begin - if count(*) = 0 from Room where roomno = new.roomno then + if (select count(*) = 0 from Room where roomno = new.roomno) then raise exception 'Room % does not exist', new.roomno; end if; return new; @@ -286,7 +286,7 @@ begin raise exception ''no manual manipulation of HSlot''; end if; if tg_op = ''UPDATE'' and new.hubname != old.hubname then - if count(*) > 0 from Hub where name = old.hubname then + if (select count(*) > 0 from Hub where name = old.hubname) then raise exception ''no manual manipulation of HSlot''; end if; end if; @@ -942,12 +942,12 @@ begin return retval || pslot_backlink_view(psrec.slotlink); end if; if sltype = ''HS'' then - retval := comment from Hub H, HSlot HS + retval := (select comment from Hub H, HSlot HS where HS.slotname = psrec.slotlink - and H.name = HS.hubname; + and H.name = HS.hubname); retval := retval || '' slot ''; - retval := retval || slotno::text from HSlot - where slotname = psrec.slotlink; + retval := (select retval || slotno::text from HSlot + where slotname = psrec.slotlink); return retval; end if; return psrec.slotlink; @@ -2388,7 +2388,7 @@ begin end $$ language plpgsql; select namedparmcursor_test7(); ERROR: division by zero -CONTEXT: SQL expression "42/0 AS p1, 77 AS p2" +CONTEXT: PL/pgSQL stetement OPEN "42/0 AS p1, 77 AS p2" PL/pgSQL function namedparmcursor_test7() line 6 at OPEN -- check that line comments work correctly within the argument list -- (this used to require a special hack in the code; it no longer does, diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out index 8c4da95508..6335487546 100644 --- a/src/test/regress/expected/stats_ext.out +++ b/src/test/regress/expected/stats_ext.out @@ -327,7 +327,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt; CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1; DO $$ DECLARE - relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass; + relname text := (select reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass); BEGIN EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname; EXCEPTION WHEN wrong_object_type THEN diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index 7f5757e89c..3458dad174 100644 --- a/src/test/regress/expected/transactions.out +++ b/src/test/regress/expected/transactions.out @@ -543,7 +543,7 @@ select * from xacttest; rollback; -- Now the same test with plpgsql (since it depends on SPI which is different) create or replace function max_xacttest() returns smallint language plpgsql as -'begin return max(a) from xacttest; end' stable; +'begin return (select max(a) from xacttest); end' stable; begin; update xacttest set a = max_xacttest() + 10 where a > 0; select * from xacttest; @@ -558,7 +558,7 @@ select * from xacttest; rollback; create or replace function max_xacttest() returns smallint language plpgsql as -'begin return max(a) from xacttest; end' volatile; +'begin return (select max(a) from xacttest); end' volatile; begin; update xacttest set a = max_xacttest() + 10 where a > 0; select * from xacttest; diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 8c8fa27a6a..62a6c49d81 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -1667,7 +1667,7 @@ LANGUAGE plpgsql AS $$ DECLARE v_relfilenode oid; BEGIN - v_relfilenode := relfilenode FROM pg_class WHERE oid = p_tablename; + v_relfilenode := (select relfilenode FROM pg_class WHERE oid = p_tablename); EXECUTE p_ddl; diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql index 4b2f11dcc6..a3dbd93468 100644 --- a/src/test/regress/sql/plancache.sql +++ b/src/test/regress/sql/plancache.sql @@ -81,7 +81,7 @@ create temp view v1 as create function cache_test_2() returns int as $$ begin - return f1 from v1; + return (select f1 from v1); end$$ language plpgsql; select cache_test_2(); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 18c91572ae..fbc21b1f52 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -161,7 +161,7 @@ create trigger tg_room_ad after delete -- ************************************************************ create function tg_wslot_biu() returns trigger as $$ begin - if count(*) = 0 from Room where roomno = new.roomno then + if (select count(*) = 0 from Room where roomno = new.roomno) then raise exception 'Room % does not exist', new.roomno; end if; return new; @@ -348,7 +348,7 @@ begin raise exception ''no manual manipulation of HSlot''; end if; if tg_op = ''UPDATE'' and new.hubname != old.hubname then - if count(*) > 0 from Hub where name = old.hubname then + if (select count(*) > 0 from Hub where name = old.hubname) then raise exception ''no manual manipulation of HSlot''; end if; end if; @@ -1071,12 +1071,12 @@ begin return retval || pslot_backlink_view(psrec.slotlink); end if; if sltype = ''HS'' then - retval := comment from Hub H, HSlot HS + retval := (select comment from Hub H, HSlot HS where HS.slotname = psrec.slotlink - and H.name = HS.hubname; + and H.name = HS.hubname); retval := retval || '' slot ''; - retval := retval || slotno::text from HSlot - where slotname = psrec.slotlink; + retval := (select retval || slotno::text from HSlot + where slotname = psrec.slotlink); return retval; end if; return psrec.slotlink; diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql index 0c08a6cc42..36cfa31c9f 100644 --- a/src/test/regress/sql/stats_ext.sql +++ b/src/test/regress/sql/stats_ext.sql @@ -209,7 +209,7 @@ CREATE STATISTICS tststats.s8 ON a, b FROM tststats.pt; CREATE STATISTICS tststats.s9 ON a, b FROM tststats.pt1; DO $$ DECLARE - relname text := reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass; + relname text := (select reltoastrelid::regclass FROM pg_class WHERE oid = 'tststats.t'::regclass); BEGIN EXECUTE 'CREATE STATISTICS tststats.s10 ON a, b FROM ' || relname; EXCEPTION WHEN wrong_object_type THEN diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql index 51ae1b31b3..1981955ac6 100644 --- a/src/test/regress/sql/transactions.sql +++ b/src/test/regress/sql/transactions.sql @@ -320,7 +320,7 @@ rollback; -- Now the same test with plpgsql (since it depends on SPI which is different) create or replace function max_xacttest() returns smallint language plpgsql as -'begin return max(a) from xacttest; end' stable; +'begin return (select max(a) from xacttest); end' stable; begin; update xacttest set a = max_xacttest() + 10 where a > 0; @@ -328,7 +328,7 @@ select * from xacttest; rollback; create or replace function max_xacttest() returns smallint language plpgsql as -'begin return max(a) from xacttest; end' volatile; +'begin return (select max(a) from xacttest); end' volatile; begin; update xacttest set a = max_xacttest() + 10 where a > 0;
^ permalink raw reply [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2024-06-10 15:00  Pavel Stehule <pavel.stehule@gmail.com>
  parent: Pavel Stehule <pavel.stehule@gmail.com>
  0 siblings, 0 replies; 13+ messages in thread

From: Pavel Stehule @ 2024-06-10 15:00 UTC (permalink / raw)
  To: Mor Lehr <mor.lehr@deel.com>; +Cc: Erik Wienhold <ewie@ewie.name>; Tom Lane <tgl@sss.pgh.pa.us>; David G. Johnston <david.g.johnston@gmail.com>; pgsql-bugs@lists.postgresql.org <pgsql-bugs@lists.postgresql.org>

po 10. 6. 2024 v 13:56 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:

> Hi
>
> út 4. 6. 2024 v 8:39 odesílatel Mor Lehr <mor.lehr@deel.com> napsal:
>
>> How about inventing an opt-in strict mode
>>
>>
>> That can be useful at the session level, because we use anonymous blocks
>> quite often.
>> I assume if such a setting existed - we would have used it in the
>> original scenario.
>>
>> Thanks again,
>> -Mor
>>
>> On Mon, Jun 3, 2024 at 9:12 PM Pavel Stehule <pavel.stehule@gmail.com>
>> wrote:
>>
>>>
>>>
>>> po 3. 6. 2024 v 18:46 odesílatel Erik Wienhold <ewie@ewie.name> napsal:
>>>
>>>> On 2024-06-03 00:18 +0200, Tom Lane wrote:
>>>> > "David G. Johnston" <david.g.johnston@gmail.com> writes:
>>>> > > I think you just wrote the equivalent of:
>>>> > > l_cnt := (select 1 as delete from foo3 where id=1);
>>>> > > Which is a valid query.
>>>> >
>>>> > Still another example of the folly of letting AS be optional.
>>>> > I don't suppose we can ever undo that though.
>>>>
>>>> How about inventing an opt-in strict mode (like in Perl or JavaScript)
>>>> that prevents certain footguns?  For example, disallowing bare column
>>>> labels.
>>>>
>>>> That could be enabled for the current session or transaction:
>>>>
>>>>     SET strict_parsing = { on | off };
>>>>
>>>> Or just for individual routines:
>>>>
>>>>     CREATE PROCEDURE myproc()
>>>>         SET strict_parsing = { on | off }
>>>>         LANGUAGE plpgsql ...
>>>>
>>>>
>>> Probably it is not bad idea - it can be generally useful
>>>
>>> But I think it is better to introduce a new entry for plpgsql
>>> expressions in gram.y.
>>>
>>> Unfortunately it is not a compatible change. Years ago was popular to
>>> use a pattern
>>>
>>> a := tab.a FROM tab
>>>
>>> instead correct
>>>
>>> a := (SELECT tab.a FROM tab)
>>>
>>> or
>>>
>>> SELECT tab.a FROM tab INTO a;
>>>
>>> Regards
>>>
>>> Pavel
>>>
>>
> I wrote an experimental patch that enforces strict syntax for PL/pgSQL
> expressions. This patch is a proof concept and shows impacts of the change
> (check-world tests passed)
>
> Unfortunately it introduces break compatibility - with this patch the odd
> syntax (that is undocumented) is not supported. Something like
>
> DECLARE _x int := x FROM foo WHERE id = _arg;
>
> should not be written in strict mode;
>
> This patch very well fix reported issue:
>
> (2024-06-10 12:06:43) postgres=# CREATE TABLE foo3(id serial PRIMARY key,
> txt text);
> CREATE TABLE
> (2024-06-10 12:07:13) postgres=# INSERT INTO foo3 (txt) VALUES
> ('aaa'),('bbb');
> INSERT 0 2
> (2024-06-10 12:07:33) postgres=# DO $$
> DECLARE
>     l_cnt int;
> BEGIN
>     l_cnt := 1
>     DELETE FROM foo3 WHERE id=1;
> END; $$;
> ERROR:  syntax error at or near "DELETE"
> LINE 11:     DELETE FROM foo3 WHERE id=1;
>              ^
>
> Personally, I think it can be a strong step forward (we can use deeper
> integration of plpgsql with SQL parser which was not possible before).
>
> Because it introduces a compatibility break I don't propose to use it by
> default. I see two possible ways, how this check can be used:
>
> 1. we can use plpgsql.extra_errors (
> https://www.postgresql.org/docs/current/plpgsql-development-tips.html#PLPGSQL-EXTRA-CHECKS
> ) and introduce a check named (maybe) strict_expr_syntax. Because in this
> case the warning cannot be raised, then this check cannot be used in
> plpgsql.extra_warnings. I think it can work nicely.
>
> 2. if @1 will not be possible, the minimalist implementation can be based
> on a new public entry to SQL parser.  In this case, I can do the proposed
> check at least from plpgsql_check.
>
> Do you see any other possibilities?
>
> This patch is just a proof concept. I didn't implement any mechanism to
> switch from default mode to strict mode (I don't propose strict mode as
> default) now.
>
> I think it can increase the robustness of plpgsql, on the other hand it
> introduces compatibility breaks and I understand related problems.
>
> The change of definition of PLpgSQL_Expr has an impact on OPEN and CASE
> PLpgSQL statements that use multicolumn results.
>

Note - the SQL/PSM standard allow syntax

SET var = (SELECT col FROM tab)

as shorter variant of

SELECT col INTO var FROM tab ;

so

var := (SELECT col FROM tab)

is +/- ANSI/SQL syntax. It is not my invention. The subquery is used as a
guard against returning multiple rows.

The proprietary Postgre syntax is a little bit faster - 80us x 95 us,
doesn't raise an exception when returning more rows (I am not sure if this
is any benefit or not - it is possibly dangerous, but it can reduce the
necessity of subtransaction in some patterns). Instead of proprietary
syntax, SELECT INTO can be used for these cases.

Regards

Pavel



> Regards
>
> Pavel
>
>
>>
>>>
>>>
>>>> --
>>>> Erik
>>>>
>>>>
>>>>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2025-02-06 06:47  Pavel Stehule <pavel.stehule@gmail.com>
  parent: Mor Lehr <mor.lehr@deel.com>
  0 siblings, 1 reply; 13+ messages in thread

From: Pavel Stehule @ 2025-02-06 06:47 UTC (permalink / raw)
  To: Mor Lehr <mor.lehr@deel.com>; +Cc: David G. Johnston <david.g.johnston@gmail.com>; pgsql-bugs@lists.postgresql.org

Hi

ne 2. 6. 2024 v 18:25 odesílatel Mor Lehr <mor.lehr@deel.com> napsal:

> Thanks for the reference.
> We learn new stuff every day.
>
> You can close the case.
> Thanks, Mor
>
>
plpgsql_check can now detect this issue

https://okbob.blogspot.com/2025/02/plpgsqlcheck-raise-warning-when-syntax.html

Regards

Pavel


> On Sun, Jun 2, 2024, 18:31 David G. Johnston <david.g.johnston@gmail.com>
> wrote:
>
>> On Sunday, June 2, 2024, Mor Lehr <mor.lehr@deel.com> wrote:
>>
>>> Thanks for the prompt reply.
>>> Can you please refer me to the section in the documentation that
>>> describes this behavior?
>>> This (automatically interperting 1 as select 1) is totally an unexpected
>>> behavior for me.
>>>
>>
>>
>> https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-ASSIGNMENT
>>
>> “As explained previously, the expression in such a statement is evaluated
>> by means of an SQL SELECT command sent to the main database engine.”
>>
>> David J.
>>
>>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread

* Re: Missing semicolumn in anonymous plpgsql block does not raise syntax error
@ 2025-02-06 10:10  Mor Lehr <mor.lehr@deel.com>
  parent: Pavel Stehule <pavel.stehule@gmail.com>
  0 siblings, 0 replies; 13+ messages in thread

From: Mor Lehr @ 2025-02-06 10:10 UTC (permalink / raw)
  To: Pavel Stehule <pavel.stehule@gmail.com>; +Cc: David G. Johnston <david.g.johnston@gmail.com>; pgsql-bugs@lists.postgresql.org

Thanks for not letting this be forgotten :)
Unfortunately, plpgsql_check does not help me because it's not supported on
AWS.
I also saw you wrote a patch which is still pending, I will probably have
to wait for that.

Thanks again,
Mor

On Thu, Feb 6, 2025 at 7:48 AM Pavel Stehule <pavel.stehule@gmail.com>
wrote:

> Hi
>
> ne 2. 6. 2024 v 18:25 odesílatel Mor Lehr <mor.lehr@deel.com> napsal:
>
>> Thanks for the reference.
>> We learn new stuff every day.
>>
>> You can close the case.
>> Thanks, Mor
>>
>>
> plpgsql_check can now detect this issue
>
>
> https://okbob.blogspot.com/2025/02/plpgsqlcheck-raise-warning-when-syntax.html
>
> Regards
>
> Pavel
>
>
>> On Sun, Jun 2, 2024, 18:31 David G. Johnston <david.g.johnston@gmail.com>
>> wrote:
>>
>>> On Sunday, June 2, 2024, Mor Lehr <mor.lehr@deel.com> wrote:
>>>
>>>> Thanks for the prompt reply.
>>>> Can you please refer me to the section in the documentation that
>>>> describes this behavior?
>>>> This (automatically interperting 1 as select 1) is totally an
>>>> unexpected behavior for me.
>>>>
>>>
>>>
>>> https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-ASSIGNMENT
>>>
>>> “As explained previously, the expression in such a statement is
>>> evaluated by means of an SQL SELECT command sent to the main database
>>> engine.”
>>>
>>> David J.
>>>
>>>

^ permalink  raw  reply  [nested|flat] 13+ messages in thread


end of thread, other threads:[~2025-02-06 10:10 UTC | newest]

Thread overview: 13+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2024-06-02 11:24 Missing semicolumn in anonymous plpgsql block does not raise syntax error Mor Lehr <mor.lehr@deel.com>
2024-06-02 15:19 ` David G. Johnston <david.g.johnston@gmail.com>
2024-06-02 15:26   ` Mor Lehr <mor.lehr@deel.com>
2024-06-02 15:31     ` David G. Johnston <david.g.johnston@gmail.com>
2024-06-02 15:42       ` Mor Lehr <mor.lehr@deel.com>
2025-02-06 06:47         ` Pavel Stehule <pavel.stehule@gmail.com>
2025-02-06 10:10           ` Mor Lehr <mor.lehr@deel.com>
2024-06-02 22:18   ` Tom Lane <tgl@sss.pgh.pa.us>
2024-06-03 16:45     ` Erik Wienhold <ewie@ewie.name>
2024-06-03 18:11       ` Pavel Stehule <pavel.stehule@gmail.com>
2024-06-04 06:39         ` Mor Lehr <mor.lehr@deel.com>
2024-06-10 11:56           ` Pavel Stehule <pavel.stehule@gmail.com>
2024-06-10 15:00             ` Pavel Stehule <pavel.stehule@gmail.com>

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox