Re: Rethinking plpgsql's assignment implementation - Mailing list pgsql-hackers
From | Pavel Stehule |
---|---|
Subject | Re: Rethinking plpgsql's assignment implementation |
Date | |
Msg-id | CAFj8pRDi-WT2VaR+DpyAe+H3Pf9xgAozSj+SzhrZYuFSZ3u_Jw@mail.gmail.com Whole thread Raw |
In response to | Re: Rethinking plpgsql's assignment implementation (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Rethinking plpgsql's assignment implementation
|
List | pgsql-hackers |
ne 13. 12. 2020 v 22:41 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
I wrote:
> So my idea here is to add a parsing-mode option to raw_parser(),
> which would be an enum with values like "normal SQL statement",
> "expression only", "type name", "plpgsql assignment statement".
Here's a fleshed-out patch series that attacks things that way.
I'm a lot better pleased with this than with my original approach.
0001 creates the basic infrastructure for "raw parse modes", and as
proof of concept simplifies typeStringToTypeName(). There's a minor
functional improvement there, which is that we can now use the core
parser's error cursor position, so instead of
regression=# do $$ declare x int[23/] ; begin end $$;
ERROR: syntax error at or near "/"
LINE 1: do $$ declare x int[23/] ; begin end $$;
^
CONTEXT: invalid type name "int[23/] "
you get
regression=# do $$ declare x int[23/] ; begin end $$;
ERROR: syntax error at or near "/"
LINE 1: do $$ declare x int[23/] ; begin end $$;
^
CONTEXT: invalid type name "int[23/] "
It's possible we could dispense with the error context callback
in typeStringToTypeName altogether, but I've not experimented much.
0002 tackles the next problem, which is to make this feature accessible
through SPI. There are a couple of possibly-controversial choices here.
Following the principle that we should avoid changing documented SPI
interfaces, we need a new version of SPI_prepare to pass RawParseMode
through. This'll be the fourth one :-(, so I decided it was time to
try to make a definition that can stay API-compatible through future
changes. So it takes a struct of options, and I added a promise that
zeroing the struct is enough to guarantee forward compatibility
through future additions.
This leaves both of the previous iterations, SPI_prepare_cursor
and SPI_prepare_params, unused anywhere in the core code.
I suppose we can't kill them (codesearch.debian.net knows of some
external uses) but I propose to mark them deprecated, with an eye
to at least removing their documentation someday.
I did not want to add a RawParseMode parameter to pg_parse_query(),
because that would have affected a larger number of unrelated modules,
and it would not have been great from a header-inclusion footprint
standpoint either. So I chose to pass down the mode from SPI by
having it just call raw_parser() directly instead of going through
pg_parse_query(). Perhaps this is a modularity violation, or perhaps
there's somebody who really wants the extra tracing overhead in
pg_parse_query() to apply to SPI queries. I'm open to discussing
whether this should be done differently.
(However, having made these two patches, I'm now wondering whether
there is any rhyme or reason to the existing state of affairs
with some callers going through pg_parse_query() while others use
raw_parser() directly. It's hard to knock making a different
choice in spi.c unless we have a coherent policy about which to
use where.)
Next, 0003 invents a raw parse mode for plpgsql expressions (which,
in some contexts, can be pretty nearly whole SELECT statements),
and uses that to get plpgsql out of the business of prefixing
"SELECT " to user-written text. I would not have bothered with this
as a standalone fix, but I think it does make for less-confusing
error messages --- we've definitely had novices ask "where'd this
SELECT come from?" in the past. (I cheated a bit on PERFORM, though.
Unlike other places, it needs to allow UNION, so it can't use the
same restricted syntax.)
0004 then reimplements plpgsql assignment. This is essentially the same
patch I submitted before, but redesigned to work with the infrastructure
from 0001-0003.
0005 adds documentation and test cases. It also fixes a couple
of pre-existing problems that the plpgsql parser had with assigning
to sub-fields of record fields, which I discovered while making the
tests.
Finally, 0006 removes plpgsql's ARRAYELEM datum type, on the grounds
that we don't need it anymore. This might be a little controversial
too, because there was still one way to reach the code: GET DIAGNOSTICS
with an array element as target would do so. However, that seems like
a pretty weird corner case. Reviewing the git history, I find that
I added support for that in commit 55caaaeba; but a check of the
associated discussion shows that there was no actual user request for
that, I'd just done it because it was easy and seemed more symmetric.
The amount of code involved here seems way more than is justified by
that one case, so I think we should just take it out and lose the
"feature". (I did think about whether GET DIAGNOSTICS could be
reimplemented on top of the new infrastructure, but it wouldn't be
easy because we don't have a SQL-expression representation of the
GET DIAGNOSTICS values. Moreover, going in that direction would add
an expression evaluation, making GET DIAGNOSTICS slower. So I think
we should just drop it.)
It is a really great patch. I did fast check and I didn't find any functionality issue
--
-- Name: footype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.footype AS (
a integer,
b integer
);
ALTER TYPE public.footype OWNER TO pavel;
--
-- Name: bootype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.bootype AS (
a integer,
f public.footype
);
ALTER TYPE public.bootype OWNER TO pavel;
--
-- Name: cootype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.cootype AS (
a integer,
b integer[]
);
ALTER TYPE public.cootype OWNER TO pavel;
--
-- Name: dootype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.dootype AS (
a integer,
b public.footype,
c public.footype[]
);
ALTER TYPE public.dootype OWNER TO pavel;
--
-- PostgreSQL database dump complete
--
-- Name: footype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.footype AS (
a integer,
b integer
);
ALTER TYPE public.footype OWNER TO pavel;
--
-- Name: bootype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.bootype AS (
a integer,
f public.footype
);
ALTER TYPE public.bootype OWNER TO pavel;
--
-- Name: cootype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.cootype AS (
a integer,
b integer[]
);
ALTER TYPE public.cootype OWNER TO pavel;
--
-- Name: dootype; Type: TYPE; Schema: public; Owner: pavel
--
CREATE TYPE public.dootype AS (
a integer,
b public.footype,
c public.footype[]
);
ALTER TYPE public.dootype OWNER TO pavel;
--
-- PostgreSQL database dump complete
--
postgres=# do $$
<<lab>>
declare
a footype[];
b bootype;
ba bootype[];
c cootype[];
d dootype[];
x int default 1;
begin
a[10] := row(10,20);
a[11] := (30,40);
a[3] := (0,0);
a[3].a := 100;
raise notice '%', a;
b.a := 100;
b.f.a := 1000;
raise notice '%', b;
ba[0] := b;
ba[0].a = 33; ba[0].f := row(33,33);
lab.ba[0].f.a := 1000000;
raise notice '%', ba;
c[0].a := 10000;
c[0].b := ARRAY[1,2,4];
lab.c[0].b[1] := 10000;
raise notice '% %', c, c[0].b[x];
d[0].a := 100;
d[0].b.a := 101;
d[0].c[x+1].a := 102;
raise notice '%', d;
end;
$$;
NOTICE: [3:11]={"(100,0)",NULL,NULL,NULL,NULL,NULL,NULL,"(10,20)","(30,40)"}
NOTICE: (100,"(1000,)")
NOTICE: [0:0]={"(33,\"(1000000,33)\")"}
NOTICE: [0:0]={"(10000,\"{10000,2,4}\")"} 10000
NOTICE: [0:0]={"(100,\"(101,)\",\"[2:2]={\"\"(102,)\"\"}\")"}
DO
<<lab>>
declare
a footype[];
b bootype;
ba bootype[];
c cootype[];
d dootype[];
x int default 1;
begin
a[10] := row(10,20);
a[11] := (30,40);
a[3] := (0,0);
a[3].a := 100;
raise notice '%', a;
b.a := 100;
b.f.a := 1000;
raise notice '%', b;
ba[0] := b;
ba[0].a = 33; ba[0].f := row(33,33);
lab.ba[0].f.a := 1000000;
raise notice '%', ba;
c[0].a := 10000;
c[0].b := ARRAY[1,2,4];
lab.c[0].b[1] := 10000;
raise notice '% %', c, c[0].b[x];
d[0].a := 100;
d[0].b.a := 101;
d[0].c[x+1].a := 102;
raise notice '%', d;
end;
$$;
NOTICE: [3:11]={"(100,0)",NULL,NULL,NULL,NULL,NULL,NULL,"(10,20)","(30,40)"}
NOTICE: (100,"(1000,)")
NOTICE: [0:0]={"(33,\"(1000000,33)\")"}
NOTICE: [0:0]={"(10000,\"{10000,2,4}\")"} 10000
NOTICE: [0:0]={"(100,\"(101,)\",\"[2:2]={\"\"(102,)\"\"}\")"}
DO
Regards
Pavel
regards, tom lane
pgsql-hackers by date: