Re: PL/pgSQL: Spurious 22P02 error on "select col into var" when col is user-defined type

From: Pavel Stehule <pavel(dot)stehule(at)gmail(dot)com>
To: Bryn Llewellyn <bryn(at)yugabyte(dot)com>
Cc: Tom Lane PostgreSQL <tgl(at)sss(dot)pgh(dot)pa(dot)us>, pgsql-general list <pgsql-general(at)lists(dot)postgresql(dot)org>
Subject: Re: PL/pgSQL: Spurious 22P02 error on "select col into var" when col is user-defined type
Date: 2021-08-10 19:18:02
Message-ID: CAFj8pRBTx46Q9Y7xKeWY5X_MQxwcv1B2QrXkFbRZDnoSBZ0Q0w@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

út 10. 8. 2021 v 19:48 odesílatel Bryn Llewellyn <bryn(at)yugabyte(dot)com> napsal:

> pavel(dot)stehule(at)gmail(dot)com wrote:
>
> tgl(at)sss(dot)pgh(dot)pa(dot)us wrote:
>
> *pavel(dot)stehule(at)gmail(dot)com <pavel(dot)stehule(at)gmail(dot)com> wrote:*
>
> Some errors like this, but not this can be detected by plpgsql_check
> https://github.com/okbob/plpgsql_check
> probably the heuristic for type check is not complete.
>
>
> STRICTMULTIASSIGNMENT would detect most cases of this, except that the
> condition is checked too late. We'd need to count the fields *before*
> trying to assign values, not after.
>
> In the meantime, it does seem like the docs could be more explicit about
> this, and perhaps give an example showing the (x).* solution.
>
>
> Yes, a more detailed explanation of this behavior can be nice. There can
> be an example of value unnesting, but I think so for this case, there
> should be mainly an example of ANSI assign syntax.
>
> var := (SELECT x FROM ..)
>
> This syntax has advantages so is not amigonuous for this case, and
> explicit unnesting is not necessary (and it is not possible). Moreover,
> this is ANSI SQL syntax.
>
>
> Consider this example:
>
> create type type1 as (a1 int, a2 int);
> create table tab1(k int primary key, b boolean not null, t type1 not null);
> insert into tab1(k, b, t) values(1, true, (10, 20));
> select b::text, t::text from tab1 where k = 1;
>
> It seems to be perfectly plausible—and so it seems equally plausible that
> you'd want to do it using PL/pgSQL. Each of these two alternatives,
> inspired by the advice that I got in this thread, works and produces the
> expected output:
>

Yes, this works. This syntax is not ambiguous.

> do $body$
> declare
> r record;
> begin
> select ((b, t)).*
> into r
> from tab1
> where k = 1;
> raise info 'Alt 1: % | %', r.f1::text, r.f2::text;
>
> r := (
> select (b, t)
> from tab1
> where k = 1);
> raise info 'Alt 2: % | %', r.f1::text, r.f2::text;
> end;
> $body$;
>
> It feels a smidge uncomfortable because I'm forced to use reference by
> field position (f1, f2) rather than by field name (b, t). But reference by
> position is commonplace in PostgreSQL (for example, in the PREPARE
> statement). So I'spose that I have to learn to like it.
>

postgres=# do $$
declare r record;
begin
select 10 as a, 20 as b into r;
raise notice '% %', r.a, r.b;
end;
$$;
NOTICE: 10 20
DO

The composite value always has structure, and types, but sometimes it can
lose labels. You can push labels by casting

r := (select (b, t) -- this is dynamic composity value, but without labels
- the scalar value doesn't hold label

or

r := (select (b, t)::type1 -- it is composite with labels again

>
> Do you (all) think that the "Alt 1" and "Alt 2" that I show are the best
> way to implement the requirement? I prefer "Alt 2" because it doesn't have
> the clutter (and the burden for understanding and readability) of the extra
> parentheses and the ".*".
>

I prefer Alt 2 too.

>
> I can get the reference by field name that I prefer with a schema-level
> type:
>
> create type tmp as (b text, t type1);
>
> and by declaring "r" with this data type. But this is a greater discomfort
> than using the dynamically shaped "record" because it needs you to create a
> dedicated schema-level type for every new SELCT list that you come need.
>

When It is possible I use a record type - some years ago, the work with
this type was a little bit slower, but not now. The work with this type is
little bit safer - because it gets real labels. Values with declared
composite types uses positional assignment, that is not too safe.

create table foo(a int, b int);
create type footype as (a int, b int);
insert into foo values(10, 200);

postgres=# do $$
declare f footype;
r record;
begin
select b, a from foo into f;
select b, a from foo into r;
raise notice 'f.a: %, f.b: %', f.a, f.b;
raise notice 'r.a: %, r.b: %', r.a, r.b;
end;
$$;
NOTICE: f.a: 200, f.b: 10
NOTICE: r.a: 10, r.b: 200
DO
postgres=# select * from foo;
┌────┬─────┐
│ a │ b │
╞════╪═════╡
│ 10 │ 200 │
└────┴─────┘
(1 row)

But sometimes explicit type is necessary - when you want to return
composite value and when you want to work with composite outside function,
or when you want to serialize, or deserialize composite value to/from json.

When you work with composite values, is good to enable warnings

https://www.postgresql.org/docs/current/plpgsql-development-tips.html#PLPGSQL-EXTRA-CHECKS

Regards

Pavel

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Pavel Stehule 2021-08-10 19:25:01 Re: PL/pgSQL: Spurious 22P02 error on "select col into var" when col is user-defined type
Previous Message Bryn Llewellyn 2021-08-10 17:48:37 Re: PL/pgSQL: Spurious 22P02 error on "select col into var" when col is user-defined type