pg14 psql broke \d datname.nspname.relname
This commit broke psql \d datname.nspname.relname
commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868
Author: Robert Haas <rhaas@postgresql.org>
Date: Wed Feb 3 13:19:41 2021 -0500
Factor pattern-construction logic out of processSQLNamePattern.
...
patternToSQLRegex is a little more general than what is required
by processSQLNamePattern. That function is only interested in
patterns that can have up to 2 parts, a schema and a relation;
but patternToSQLRegex can limit the maximum number of parts to
between 1 and 3, so that patterns can look like either
"database.schema.relation", "schema.relation", or "relation"
depending on how it's invoked and what the user specifies.
processSQLNamePattern only passes two buffers, so works exactly
the same as before, always interpreting the pattern as either
a "schema.relation" pattern or a "relation" pattern. But,
future callers can use this function in other ways.
|$ LD_LIBRARY_PATH=tmp_install/usr/local/pgsql/lib/ src/bin/psql/psql -h /tmp regression
|psql (15devel)
|Type "help" for help.
|regression=# \d regresion.public.bit_defaults
|Did not find any relation named "regresion.public.bit_defaults".
|regression=# \d public.bit_defaults
| Table "public.bit_defaults"
|...
This worked before v14 (even though the commit message says otherwise).
|$ /usr/lib/postgresql/13/bin/psql -h /tmp regression
|psql (13.2 (Debian 13.2-1.pgdg100+1), server 15devel)
|...
|regression=# \d regresion.public.bit_defaults
| Table "public.bit_defaults"
|...
--
Justin
On Oct 11, 2021, at 2:24 PM, Justin Pryzby <pryzby@telsasoft.com> wrote:
This commit broke psql \d datname.nspname.relname
commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868
Author: Robert Haas <rhaas@postgresql.org>
Date: Wed Feb 3 13:19:41 2021 -0500Factor pattern-construction logic out of processSQLNamePattern.
...
patternToSQLRegex is a little more general than what is required
by processSQLNamePattern. That function is only interested in
patterns that can have up to 2 parts, a schema and a relation;
but patternToSQLRegex can limit the maximum number of parts to
between 1 and 3, so that patterns can look like either
"database.schema.relation", "schema.relation", or "relation"
depending on how it's invoked and what the user specifies.processSQLNamePattern only passes two buffers, so works exactly
the same as before, always interpreting the pattern as either
a "schema.relation" pattern or a "relation" pattern. But,
future callers can use this function in other ways.|$ LD_LIBRARY_PATH=tmp_install/usr/local/pgsql/lib/ src/bin/psql/psql -h /tmp regression
|psql (15devel)
|Type "help" for help.
|regression=# \d regresion.public.bit_defaults
|Did not find any relation named "regresion.public.bit_defaults".
|regression=# \d public.bit_defaults
| Table "public.bit_defaults"
|...This worked before v14 (even though the commit message says otherwise).
|$ /usr/lib/postgresql/13/bin/psql -h /tmp regression
|psql (13.2 (Debian 13.2-1.pgdg100+1), server 15devel)
|...
|regression=# \d regresion.public.bit_defaults
| Table "public.bit_defaults"
|...
I can only assume that you are intentionally misspelling "regression" as "regresion" (with only one "s") as part of the test. I have not checked if that worked before v14, but if it ignored the misspelled database name before v14, and it rejects it now, I'm not sure that counts as a bug.
Am I misunderstanding your bug report?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Mark Dilger <mark.dilger@enterprisedb.com> writes:
I can only assume that you are intentionally misspelling "regression" as "regresion" (with only one "s") as part of the test. I have not checked if that worked before v14, but if it ignored the misspelled database name before v14, and it rejects it now, I'm not sure that counts as a bug.
Doesn't work with the correct DB name, either:
regression=# \d public.bit_defaults
Table "public.bit_defaults"
Column | Type | Collation | Nullable | Default
--------+----------------+-----------+----------+---------------------
b1 | bit(4) | | | '1001'::"bit"
b2 | bit(4) | | | '0101'::"bit"
b3 | bit varying(5) | | | '1001'::bit varying
b4 | bit varying(5) | | | '0101'::"bit"
regression=# \d regression.public.bit_defaults
Did not find any relation named "regression.public.bit_defaults".
regards, tom lane
On Oct 11, 2021, at 3:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Doesn't work with the correct DB name, either:
regression=# \d public.bit_defaults
Table "public.bit_defaults"
Column | Type | Collation | Nullable | Default
--------+----------------+-----------+----------+---------------------
b1 | bit(4) | | | '1001'::"bit"
b2 | bit(4) | | | '0101'::"bit"
b3 | bit varying(5) | | | '1001'::bit varying
b4 | bit varying(5) | | | '0101'::"bit"regression=# \d regression.public.bit_defaults
Did not find any relation named "regression.public.bit_defaults".
REL_13_STABLE appears to accept any amount of nonsense you like:
foo=# \d nonesuch.foo.a.b.c.d.bar.baz
Table "bar.baz"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
Is this something we're intentionally supporting? There is no regression test covering this, else we'd have seen breakage in the build-farm.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, Oct 11, 2021 at 02:47:59PM -0700, Mark Dilger wrote:
|$ LD_LIBRARY_PATH=tmp_install/usr/local/pgsql/lib/ src/bin/psql/psql -h /tmp regression
|psql (15devel)
|Type "help" for help.
|regression=# \d regresion.public.bit_defaults
|Did not find any relation named "regresion.public.bit_defaults".
|regression=# \d public.bit_defaults
| Table "public.bit_defaults"
|...This worked before v14 (even though the commit message says otherwise).
|$ /usr/lib/postgresql/13/bin/psql -h /tmp regression
|psql (13.2 (Debian 13.2-1.pgdg100+1), server 15devel)
|...
|regression=# \d regresion.public.bit_defaults
| Table "public.bit_defaults"
|...I can only assume that you are intentionally misspelling "regression" as "regresion" (with only one "s") as part of the test. I have not checked if that worked before v14, but if it ignored the misspelled database name before v14, and it rejects it now, I'm not sure that counts as a bug.
Am I misunderstanding your bug report?
It's not intentional but certainly confusing to put a typo there.
Sorry for that (and good eyes, BTW).
In v15/master:
regression=# \d regression.public.bit_defaults
Did not find any relation named "regression.public.bit_defaults".
After reverting that commit and recompiling psql:
regression=# \d regression.public.bit_defaults
Table "public.bit_defaults"
...
In v13 psql:
regression=# \d regression.public.bit_defaults
Table "public.bit_defaults"
...
It looks like before v13 any "datname" prefix was ignored.
But now it fails to show the table because it does:
WHERE c.relname OPERATOR(pg_catalog.~) '^(public.bit_defaults)$' COLLATE pg_catalog.default
AND n.nspname OPERATOR(pg_catalog.~) '^(regression)$' COLLATE pg_catalog.default
--
Justin
On Oct 11, 2021, at 3:26 PM, Justin Pryzby <pryzby@telsasoft.com> wrote:
It looks like before v13 any "datname" prefix was ignored.
The evidence so far suggests that something is broken in v14, but it is less clear to me what the appropriate behavior is. The v14 psql is rejecting even a correctly named database.schema.table, but v13 psql accepted lots.of.nonsense.schema.table, and neither of those seems at first glance to be correct. But perhaps there are good reasons for ignoring the nonsense prefixes?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Mark Dilger <mark.dilger@enterprisedb.com> writes:
On Oct 11, 2021, at 3:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Doesn't work with the correct DB name, either:
regression=# \d regression.public.bit_defaults
Did not find any relation named "regression.public.bit_defaults".
REL_13_STABLE appears to accept any amount of nonsense you like:
Yeah, I'm pretty sure that the old rule was to just ignore whatever
appeared in the database-name position. While we could tighten that
up to insist that it match the current DB's name, I'm not sure that
I see the point. There's no near-term prospect of doing anything
useful with some other DB's name there, so being more restrictive
seems like it'll probably break peoples' scripts to little purpose.
regards, tom lane
On Oct 11, 2021, at 3:37 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
REL_13_STABLE appears to accept any amount of nonsense you like:
Yeah, I'm pretty sure that the old rule was to just ignore whatever
appeared in the database-name position. While we could tighten that
up to insist that it match the current DB's name, I'm not sure that
I see the point. There's no near-term prospect of doing anything
useful with some other DB's name there, so being more restrictive
seems like it'll probably break peoples' scripts to little purpose.
You appear correct about the old behavior. It's unclear how intentional it was. There was a schema buffer and a name buffer, and while parsing the name, if a dot was encountered, the contents just parsed were copied into the schema buffer. If multiple dots were encountered, that had the consequence of blowing away the earlier ones.
But since we allow tables and schemas with dotted names in them, I'm uncertain what \d foo.bar.baz is really asking. That could be "foo.bar"."baz", or "foo"."bar"."baz", or "foo"."bar.baz", or even "public"."foo.bar.baz". The old behavior seems a bit dangerous. There may be tables with all those names, and the user may not have meant the one that we gave them.
The v14 code is no better. It just assumes that is "foo"."bar.baz". So (with debugging statements included):
foo=# create table "foo.bar.baz" (i integer);
CREATE TABLE
foo=# \d public.foo.bar.baz
Converting "public.foo.bar.baz"
GOT "^(public)$" . "^(foo.bar.baz)$"
Table "public.foo.bar.baz"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
i | integer | | |
I expect I'll have to submit a patch restoring the old behavior, but I wonder if that's the best direction to go.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, 11 Oct 2021 at 19:35, Mark Dilger <mark.dilger@enterprisedb.com>
wrote:
But since we allow tables and schemas with dotted names in them, I'm
uncertain what \d foo.bar.baz is really asking.
FWIW, it’s absolutely clear to me that "." is a special character which has
to be quoted in order to be in an identifier. In other words, a.b.c is
three identifiers separated by two period punctuation marks; what exactly
those periods mean is another question. If somebody uses periods in their
names, they have to quote those names just as if they used capital letters
etc.
But that's just my impression. I comment at all because I remember looking
at something to do with the grammar (I think I wanted to implement ALTER …
RENAME TO newschema.newname) and noticed that a database name could be
given in the syntax.
Mark Dilger <mark.dilger@enterprisedb.com> writes:
But since we allow tables and schemas with dotted names in them, I'm uncertain what \d foo.bar.baz is really asking. That could be "foo.bar"."baz", or "foo"."bar"."baz", or "foo"."bar.baz", or even "public"."foo.bar.baz". The old behavior seems a bit dangerous. There may be tables with all those names, and the user may not have meant the one that we gave them.
You are attacking a straw man here. To use a period in an identifier,
you have to double-quote it; that's the same in SQL or \d.
regression=# create table "foo.bar" (f1 int);
CREATE TABLE
regression=# \d foo.bar
Did not find any relation named "foo.bar".
regression=# \d "foo.bar"
Table "public.foo.bar"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
f1 | integer | | |
According to a quick test, you did not manage to break that in v14.
I expect I'll have to submit a patch restoring the old behavior, but I wonder if that's the best direction to go.
I do not understand why you're even questioning that. The old
behavior had stood for a decade or two without complaints.
regards, tom lane
On Oct 11, 2021, at 4:49 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
You are attacking a straw man here. To use a period in an identifier,
you have to double-quote it; that's the same in SQL or \d.
That's a strange argument. If somebody gives an invalid identifier, we shouldn't assume they know the proper use of quotations. Somebody asking for a.b.c.d.e is clearly in the dark about something. Maybe it's the need to quote the "a.b" part separately from the "c.d.e" part, or maybe it's something else. There are lots of reasonable guesses about what they meant, and for backward compatibility reasons we define using the suffix d.e and ignoring the prefix a.b.c as the correct answer. That's a pretty arbitrary thing to do, but it has the advantage of being backwards compatible.
I expect I'll have to submit a patch restoring the old behavior, but I wonder if that's the best direction to go.
I do not understand why you're even questioning that. The old
behavior had stood for a decade or two without complaints.
I find the backward compatibility argument appealing, but since we have clients that understand the full database.schema.relation format without ignoring the database portion, our client behavior is getting inconsistent. I'd like to leave the door open for someday supporting server.database.schema.relation format, too. I was just wondering when it might be time to stop being lenient in psql and instead reject malformed identifiers.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Mon, Oct 11, 2021 at 7:09 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
I was just wondering when it might be time to stop being lenient in psql and instead reject malformed identifiers.
I suppose that I probably wouldn't have chosen this behavior in a
green field situation. But Hyrum's law tells us that there are bound
to be some number of users relying on it. I don't think that it's
worth inconveniencing those people without getting a clear benefit in
return.
Being lenient here just doesn't have much downside in practice, as
evidenced by the total lack of complaints about that lenience.
--
Peter Geoghegan
On Mon, Oct 11, 2021 at 10:33 PM Peter Geoghegan <pg@bowt.ie> wrote:
On Mon, Oct 11, 2021 at 7:09 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:I was just wondering when it might be time to stop being lenient in psql and instead reject malformed identifiers.
I suppose that I probably wouldn't have chosen this behavior in a
green field situation. But Hyrum's law tells us that there are bound
to be some number of users relying on it. I don't think that it's
worth inconveniencing those people without getting a clear benefit in
return.Being lenient here just doesn't have much downside in practice, as
evidenced by the total lack of complaints about that lenience.
I find it kind of surprising to find everyone agreeing with this
argument. I mean, PostgreSQL users are often quick to criticize MySQL
for accepting 0000-00-00 as a date, because it isn't, and you
shouldn't accept garbage and do stuff with it as if it were valid
data. But by the same argument, accepting a database name that we know
is not correct as a request to show data in the current database seems
wrong to me.
I completely agree that somebody might be relying on the fact that \d
thisdb.someschema.sometable does something sensible when logged into
thisdb, but surely no user is relying on \d
jgldslghksdghjsgkhsdgjhskg.someschema.sometable is going to just
ignore the leading gibberish. Nor do I understand why we'd want to
ignore the leading gibberish. Saying, as Tom did, that nobody has
complained about that behavior is just another way of saying that
nobody tested it. Surely if someone had, it wouldn't be like this.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
On Mon, Oct 11, 2021 at 10:33 PM Peter Geoghegan <pg@bowt.ie> wrote:
Being lenient here just doesn't have much downside in practice, as
evidenced by the total lack of complaints about that lenience.
I find it kind of surprising to find everyone agreeing with this
argument.
If the behavior v14 had implemented were "throw an error if the
first word doesn't match the current database name", perhaps nobody
would have questioned it. But that's not what we have. It's fairly
clear that neither you nor Mark thought very much about this case,
let alone tested it. Given that, I am not very pleased that you
are retroactively trying to justify breaking it by claiming that
it was already broken. It's been that way since 7.3 implemented
schemas, more or less, and nobody's complained about it. Therefore
I see little argument for changing that behavior. Changing it in
an already-released branch is especially suspect.
regards, tom lane
On Oct 12, 2021, at 7:30 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
If the behavior v14 had implemented were "throw an error if the
first word doesn't match the current database name", perhaps nobody
would have questioned it. But that's not what we have. It's fairly
clear that neither you nor Mark thought very much about this case,
let alone tested it. Given that, I am not very pleased that you
are retroactively trying to justify breaking it by claiming that
it was already broken. It's been that way since 7.3 implemented
schemas, more or less, and nobody's complained about it. Therefore
I see little argument for changing that behavior. Changing it in
an already-released branch is especially suspect.
I completely agree that we need to fix this. My question was only whether "fix" means to make it accept database.schema.table or whether it means to accept any.prefix.at.all.schema.table. It sounds like more people like the latter, so I'll go with that unless this debate rages on and a different conclusion is reached.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Oct 12, 2021 at 10:31 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
If the behavior v14 had implemented were "throw an error if the
first word doesn't match the current database name", perhaps nobody
would have questioned it. But that's not what we have. It's fairly
clear that neither you nor Mark thought very much about this case,
let alone tested it. Given that, I am not very pleased that you
are retroactively trying to justify breaking it by claiming that
it was already broken. It's been that way since 7.3 implemented
schemas, more or less, and nobody's complained about it. Therefore
I see little argument for changing that behavior. Changing it in
an already-released branch is especially suspect.
Oh, give me a break. The previous behavior obviously hasn't been
tested either, and is broken on its face. If someone *had* complained
about it, I imagine you would have promptly fixed it and likely
back-patched the fix, probably in under 24 hours from the time of the
report. I find it difficult to take seriously the contention that
anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
like \d foo.bar, or that they would even prefer that behavior over an
error message. You're carefully avoiding addressing that question in
favor of having a discussion of backward compatibility, but a better
term for what we're talking about here would be bug-compatibility.
--
Robert Haas
EDB: http://www.enterprisedb.com
Greetings,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Tue, Oct 12, 2021 at 10:31 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
If the behavior v14 had implemented were "throw an error if the
first word doesn't match the current database name", perhaps nobody
would have questioned it. But that's not what we have. It's fairly
clear that neither you nor Mark thought very much about this case,
let alone tested it. Given that, I am not very pleased that you
are retroactively trying to justify breaking it by claiming that
it was already broken. It's been that way since 7.3 implemented
schemas, more or less, and nobody's complained about it. Therefore
I see little argument for changing that behavior. Changing it in
an already-released branch is especially suspect.Oh, give me a break. The previous behavior obviously hasn't been
tested either, and is broken on its face. If someone *had* complained
about it, I imagine you would have promptly fixed it and likely
back-patched the fix, probably in under 24 hours from the time of the
report. I find it difficult to take seriously the contention that
anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
like \d foo.bar, or that they would even prefer that behavior over an
error message. You're carefully avoiding addressing that question in
favor of having a discussion of backward compatibility, but a better
term for what we're talking about here would be bug-compatibility.
I tend to agree with Robert on this particular case. Accepting random
nonsense there isn't a feature or something which really needs to be
preserved. For my 2c, I would hope that one day we will be able to
accept other database names there and if that happens, what then? We'd
"break" these cases anyway. Better to be clear that such nonsense isn't
intended to be accepted and clean that up. I do think it'd be good to
accept the current database name there as that's reasonable.
Thanks,
Stephen
On Tue, Oct 12, 2021 at 7:41 AM Robert Haas <robertmhaas@gmail.com> wrote:
Oh, give me a break. The previous behavior obviously hasn't been
tested either, and is broken on its face. If someone *had* complained
about it, I imagine you would have promptly fixed it and likely
back-patched the fix, probably in under 24 hours from the time of the
report.
You're asking us to imagine a counterfactual. But this counterfactual
bug report would have to describe a real practical problem. The
details would matter. It's reasonable to suppose that we haven't seen
such a bug report for a reason.
I can't speak for Tom. My position on this is that it's better to
leave it alone at this time, given the history, and the lack of
complaints from users.
I find it difficult to take seriously the contention that
anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
like \d foo.bar, or that they would even prefer that behavior over an
error message. You're carefully avoiding addressing that question in
favor of having a discussion of backward compatibility, but a better
term for what we're talking about here would be bug-compatibility.
Let's assume that it is bug compatibility. Is that intrinsically a bad thing?
--
Peter Geoghegan
On 10/12/21 5:19 PM, Stephen Frost wrote:
Greetings,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Tue, Oct 12, 2021 at 10:31 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
If the behavior v14 had implemented were "throw an error if the
first word doesn't match the current database name", perhaps nobody
would have questioned it. But that's not what we have. It's fairly
clear that neither you nor Mark thought very much about this case,
let alone tested it. Given that, I am not very pleased that you
are retroactively trying to justify breaking it by claiming that
it was already broken. It's been that way since 7.3 implemented
schemas, more or less, and nobody's complained about it. Therefore
I see little argument for changing that behavior. Changing it in
an already-released branch is especially suspect.Oh, give me a break. The previous behavior obviously hasn't been
tested either, and is broken on its face. If someone *had* complained
about it, I imagine you would have promptly fixed it and likely
back-patched the fix, probably in under 24 hours from the time of the
report. I find it difficult to take seriously the contention that
anyone is expecting \d dlsgjdsghj.sdhg.l.dsg.jkhsdg.foo.bar to work
like \d foo.bar, or that they would even prefer that behavior over an
error message. You're carefully avoiding addressing that question in
favor of having a discussion of backward compatibility, but a better
term for what we're talking about here would be bug-compatibility.I tend to agree with Robert on this particular case. Accepting random
nonsense there isn't a feature or something which really needs to be
preserved. For my 2c, I would hope that one day we will be able to
accept other database names there and if that happens, what then? We'd
"break" these cases anyway. Better to be clear that such nonsense isn't
intended to be accepted and clean that up. I do think it'd be good to
accept the current database name there as that's reasonable.
I am going to throw my hat in with Robert and Stephen, too. At least
for 15 if we don't want to change this behavior in back branches.
--
Vik Fearing
I understand Tom's position to be that the behavior should be changed back,
since it was 1) unintentional; and 2) breaks legitimate use (when the datname
matches current_database).
I think there's an easy answer here that would satisfy everyone; two patches:
0001 to fix the unintentional behavior change;
0002 to reject garbage input: anything with more than 3 dot-separated
components, or with 3 components where the first doesn't match
current_database.
0001 would be backpatched to v14.
If it turns out there's no consensus on 0002, or if it were really hard for
some reason, or (more likely) nobody went to the bother to implement it this
year, then that's okay.
I would prefer if it errored if the datname didn't match the current database.
After all, it would've helped me to avoid making a confusing problem report.
--
Justin
On Tue, Oct 12, 2021 at 12:44 PM Peter Geoghegan <pg@bowt.ie> wrote:
You're asking us to imagine a counterfactual. But this counterfactual
bug report would have to describe a real practical problem.
Yes. And I think this one should be held to the same standard: \d
mydb.myschema.mytable not working is potentially a real, practical
problem. \d sdlgkjdss.dsgkjsk.sdgskldjgds.myschema.mytable not working
isn't.
Let's assume that it is bug compatibility. Is that intrinsically a bad thing?
Well my view is that having the same bugs is better than having
different ones, but fixing the bugs is superior to either.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
I think there's an easy answer here that would satisfy everyone; two patches:
0001 to fix the unintentional behavior change;
0002 to reject garbage input: anything with more than 3 dot-separated
components, or with 3 components where the first doesn't match
current_database.0001 would be backpatched to v14.
If it turns out there's no consensus on 0002, or if it were really hard for
some reason, or (more likely) nobody went to the bother to implement it this
year, then that's okay.
This might work, but I fear that 0001 would end up being substantially
more complicated than a combined patch that solves both problems
together.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Oct 12, 2021, at 10:03 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
I think there's an easy answer here that would satisfy everyone; two patches:
0001 to fix the unintentional behavior change;
0002 to reject garbage input: anything with more than 3 dot-separated
components, or with 3 components where the first doesn't match
current_database.0001 would be backpatched to v14.
If it turns out there's no consensus on 0002, or if it were really hard for
some reason, or (more likely) nobody went to the bother to implement it this
year, then that's okay.This might work, but I fear that 0001 would end up being substantially
more complicated than a combined patch that solves both problems
together.
Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is. (It passes check-world and I've read it over once, but I'm not ready to stand by this as correct quite yet.) I need to add a regression test to make sure this behavior is not accidentally changed in the future, and will repost after doing so.
Attachments:
string_utils.patch.WIPapplication/octet-stream; name=string_utils.patch.WIP; x-unix-mode=0644Download
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 3efee4e7ee..d4003247df 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -942,17 +942,14 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* unquoted letters, and adjusting shell-style wildcard characters into regexp
* notation.
*
- * If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
- * contains two or more dbname/schema/name separators, we parse the portions of
- * the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * If the pattern contains dbname/schema/name separators, we interpret the
+ * final portion of the pattern as the name, the previous portion (if any) as
+ * the schema, and the portion previous to that (if any) as the database. We
+ * ignore any additional portions prior to the database.
*
- * If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
- * least one separator, we parse the first portion into schemabuf and the rest
- * into namebuf.
- *
- * Otherwise, we parse all the pattern into namebuf.
+ * If the dbnamebuf, schemabuf, or namebuf are non-NULL, we store the
+ * corresponding portion of the pattern in them, otherwise that portion is
+ * discarded.
*
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
@@ -967,9 +964,9 @@ void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
PQExpBuffer namebuf, const char *pattern, bool force_escape)
{
- PQExpBufferData buf[3];
- PQExpBuffer curbuf;
- PQExpBuffer maxbuf;
+ PQExpBuffer dbname = NULL;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer relname = NULL;
int i;
bool inquotes;
const char *cp;
@@ -983,16 +980,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
inquotes = false;
cp = pattern;
- if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
-
- curbuf = &buf[0];
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
+ relname = createPQExpBuffer();
+ appendPQExpBufferStr(relname, "^(");
while (*cp)
{
char ch = *cp;
@@ -1002,7 +991,7 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
if (inquotes && cp[1] == '"')
{
/* emit one quote, stay in inquotes mode */
- appendPQExpBufferChar(curbuf, '"');
+ appendPQExpBufferChar(relname, '"');
cp++;
}
else
@@ -1011,33 +1000,36 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
}
else if (!inquotes && isupper((unsigned char) ch))
{
- appendPQExpBufferChar(curbuf,
+ appendPQExpBufferChar(relname,
pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
- appendPQExpBufferStr(curbuf, ".*");
+ appendPQExpBufferStr(relname, ".*");
cp++;
}
else if (!inquotes && ch == '?')
{
- appendPQExpBufferChar(curbuf, '.');
+ appendPQExpBufferChar(relname, '.');
cp++;
}
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ /* Handle dbname/schema/name separators */
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
+ PQExpBuffer tmp;
+
+ appendPQExpBufferStr(relname, ")$");
+ tmp = dbname;
+ dbname = schema;
+ schema = relname;
+ relname = tmp;
+ if (relname)
+ resetPQExpBuffer(relname);
+ else
+ relname = createPQExpBuffer();
+ appendPQExpBufferStr(relname, "^(");
cp++;
}
else if (ch == '$')
@@ -1049,7 +1041,7 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* we anchor the pattern automatically there is no use-case for
* having it possess its regexp meaning.
*/
- appendPQExpBufferStr(curbuf, "\\$");
+ appendPQExpBufferStr(relname, "\\$");
cp++;
}
else
@@ -1069,30 +1061,28 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
*/
if ((inquotes || force_escape) &&
strchr("|*+?()[]{}.^$\\", ch))
- appendPQExpBufferChar(curbuf, '\\');
+ appendPQExpBufferChar(relname, '\\');
else if (ch == '[' && cp[1] == ']')
- appendPQExpBufferChar(curbuf, '\\');
+ appendPQExpBufferChar(relname, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
- appendPQExpBufferChar(curbuf, *cp++);
+ appendPQExpBufferChar(relname, *cp++);
}
}
- appendPQExpBufferStr(curbuf, ")$");
+ appendPQExpBufferStr(relname, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ appendPQExpBufferStr(namebuf, relname->data);
+ destroyPQExpBuffer(relname);
+ if (schema)
{
- curbuf--;
- appendPQExpBufferStr(schemabuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
- {
- curbuf--;
- appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ if (schemabuf)
+ appendPQExpBufferStr(schemabuf, schema->data);
+ destroyPQExpBuffer(schema);
+ }
+ if (dbname)
+ {
+ if (dbnamebuf)
+ appendPQExpBufferStr(dbnamebuf, dbname->data);
+ destroyPQExpBuffer(dbname);
}
}
On Oct 12, 2021, at 10:01 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Oct 12, 2021 at 12:44 PM Peter Geoghegan <pg@bowt.ie> wrote:
You're asking us to imagine a counterfactual. But this counterfactual
bug report would have to describe a real practical problem.Yes. And I think this one should be held to the same standard: \d
mydb.myschema.mytable not working is potentially a real, practical
problem. \d sdlgkjdss.dsgkjsk.sdgskldjgds.myschema.mytable not working
isn't.
I favor restoring the v13 behavior, but I don't think \d mydb.myschema.mytable was ever legitimate. You got exactly the same results with \d nosuchdb.myschema.mytable, meaning the user was given a false sense of security that the database name was being used to fetch the definition from the database they specified.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Oct 12, 2021 at 1:18 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is.
I guess that's not that bad. Why did we end up with the behavior that
the current comment describes this way?
"(Additional dots in the name portion are not treated as special.)"
I thought there was some reason why it needed to work that way.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Oct 12, 2021, at 10:54 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Oct 12, 2021 at 1:18 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is.
I guess that's not that bad. Why did we end up with the behavior that
the current comment describes this way?"(Additional dots in the name portion are not treated as special.)"
I thought there was some reason why it needed to work that way.
We're not talking about the parsing of string literals, but rather about the parsing of shell-style patterns. The primary caller of this logic is processSQLNamePattern(), which expects only a relname or a (schema,relname) pair, not database names nor anything else.
The pattern myschema.my.*table is not a three-part pattern, but a two part pattern, with a literal schema name and a relation name pattern. In v14 it can be seen to work as follows:
\d pg_toast.pg_.oast_2619
TOAST table "pg_toast.pg_toast_2619"
Column | Type
------------+---------
chunk_id | oid
chunk_seq | integer
chunk_data | bytea
Owning table: "pg_catalog.pg_statistic"
Indexes:
"pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
\d pg_toast.pg_.*_2619
TOAST table "pg_toast.pg_toast_2619"
Column | Type
------------+---------
chunk_id | oid
chunk_seq | integer
chunk_data | bytea
Owning table: "pg_catalog.pg_statistic"
Indexes:
"pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
In v13, neither of those matched anything (which is defensible, I guess) but the following did match, which is really nuts:
+CREATE SCHEMA g_;
+CREATE TABLE g_.oast_2619 (i integer);
+\d pg_toast..g_.oast_2619
+ Table "g_.oast_2619"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ i | integer | | |
The behavior Justin reported in the original complaint was \d regresion.public.bit_defaults, which gets handled as schema =~ /^(regresion)$/ and relname =~ /^(public.bit_defaults)$/. That gives no results for him, but I tend to think no results is defensible.
Apparently, this behavior breaks an old bug, and we need to restore the old bug and then debate this behavioral change for v15. I'd rather people had engaged in the discussion about this feature during the v14 cycle, since this patch was posted and reviewed on -hackers, and I don't recall anybody complaining about it.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Oct 12, 2021, at 10:18 AM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
Here is a WIP patch that restores the old behavior, just so you can eyeball how large it is. (It passes check-world and I've read it over once, but I'm not ready to stand by this as correct quite yet.) I need to add a regression test to make sure this behavior is not accidentally changed in the future, and will repost after doing so.
I wasn't thinking critically enough about how psql handles \d when I accepted Justin's initial characterization of the bug. The psql client has never thought about the stuff to the left of the schema name as a database name, even if some users thought about it that way. It also doesn't think about the pattern as a literal string.
The psql client's interpretation of the pattern is a bit of a chimera, following shell glob patterns for some things and POSIX regex rules for others. The reason for that is shell glob stuff gets transliterated into the corresponding POSIX syntax, but non-shell-glob stuff is left in tact, with the one outlier being dots, which have a very special interpretation. The interpretation of a dot as meaning "match one character" is not a shell glob rule but a regex one, and one that psql never supported because it split the pattern on all dots and threw away stuff to the left. There was therefore never an opportunity for an unquoted dot to make it through to the POSIX regular expression for processing. For other regex type stuff, it happily passed it through to the POSIX regex, so that the following examples work even though they contain non-shell-glob regex stuff:
v13=# create table ababab (i integer);
CREATE TABLE
v13=# \dt (ab){3}
List of relations
Schema | Name | Type | Owner
--------+--------+-------+-------------
public | ababab | table | mark.dilger
(1 row)
v13=# \dt pg_catalog.pg_clas{1,2}
List of relations
Schema | Name | Type | Owner
------------+----------+-------+-------------
pg_catalog | pg_class | table | mark.dilger
v13=# \dt pg_catalog.pg_[am]{1,3}
List of relations
Schema | Name | Type | Owner
------------+-------+-------+-------------
pg_catalog | pg_am | table | mark.dilger
(1 row)
Splitting the pattern on all the dots and throwing away any additional leftmost fields is a bug, and when you stop doing that, passing additional dots through to the POSIX regular expression for processing is the most natural thing to do. This is, in fact, how v14 works. It is a bit debatable whether treating the first dot as a separator and the additional dots as stuff to be passed through is the right thing, so we could call the v14 behavior a mis-feature, but it's not as clearcut as the discussion upthread suggested. Reverting to v13 behavior seems wrong, but I'm now uncertain how to proceed.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Oct 12, 2021 at 5:21 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
I wasn't thinking critically enough about how psql handles \d when I accepted Justin's initial characterization of the bug. The psql client has never thought about the stuff to the left of the schema name as a database name, even if some users thought about it that way. It also doesn't think about the pattern as a literal string.
I agree.
The psql client's interpretation of the pattern is a bit of a chimera, following shell glob patterns for some things and POSIX regex rules for others.
Yes. And that's pretty weird, but it's long-established precedent so
we have to deal with it.
Splitting the pattern on all the dots and throwing away any additional leftmost fields is a bug, ...
I also agree with you right up to here.
and when you stop doing that, passing additional dots through to the POSIX regular expression for processing is the most natural thing to do. This is, in fact, how v14 works. It is a bit debatable whether treating the first dot as a separator and the additional dots as stuff to be passed through is the right thing, so we could call the v14 behavior a mis-feature, but it's not as clearcut as the discussion upthread suggested. Reverting to v13 behavior seems wrong, but I'm now uncertain how to proceed.
But not this part, or at least not entirely.
If we pass the dots through to the POSIX regular expression, we can
only do that either for the table name or the schema name, not both -
either the first or last dot must mark the boundary between the two.
That means that you can't use all the same regexy things for one as
you can for the other, which is a strange system. I knew that your
patch made it do that, and I committed it that way because I didn't
think it really mattered, and also because the whole system is already
pretty strange, so what's one more bit of strangeness?
I think there are at least 3 defensible behaviors here:
1. Leave it like it is. If there is more than one dot, the extra ones
are part of one of the regex-glob thingies.
2. If there is more than one dot, error! Tell the user they messed up.
3. If there are exactly two dots, treat it as db-schema-user. Accept
it if the dbname matches the current db, and otherwise say we can't
access the named db. If there are more than two dots, then (a) it's an
error as in (2) or (b) the extra ones become part of the regex-glob
thingies as in (2).
The thing that's unprincipled about (3) is that we can't support a
regexp-glob thingy there -- we can only test for a literal string
match. And I already said what I thought was wrong with (1). But none
of these are horrible, and I don't think it really matters which one
we adopt. I don't even know if I can really rank the choices I just
listed against each other. Before I was arguing for (3a) but I'm not
sure I actually like that one particularly better.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Oct 13, 2021, at 6:24 AM, Robert Haas <robertmhaas@gmail.com> wrote:
and when you stop doing that, passing additional dots through to the POSIX regular expression for processing is the most natural thing to do. This is, in fact, how v14 works. It is a bit debatable whether treating the first dot as a separator and the additional dots as stuff to be passed through is the right thing, so we could call the v14 behavior a mis-feature, but it's not as clearcut as the discussion upthread suggested. Reverting to v13 behavior seems wrong, but I'm now uncertain how to proceed.
But not this part, or at least not entirely.
If we pass the dots through to the POSIX regular expression, we can
only do that either for the table name or the schema name, not both -
Agreed.
either the first or last dot must mark the boundary between the two.
That means that you can't use all the same regexy things for one as
you can for the other, which is a strange system.
The closest analogy is how regular expressions consider \1 \2 .. \9 as backreferences, but \10 \11 ... are dependent on context: "A multi-digit sequence not starting with a zero is taken as a back reference if it comes after a suitable subexpression (i.e., the number is in the legal range for a back reference), and otherwise is taken as octal." Taking a dot as a separator if it can be taken that way, and as a regex character otherwise, is not totally out of line with existing precedent. On the other hand, the backreference vs. octal precedent is not one I particularly like.
I knew that your
patch made it do that, and I committed it that way because I didn't
think it really mattered, and also because the whole system is already
pretty strange, so what's one more bit of strangeness?I think there are at least 3 defensible behaviors here:
1. Leave it like it is. If there is more than one dot, the extra ones
are part of one of the regex-glob thingies.2. If there is more than one dot, error! Tell the user they messed up.
I don't like the backward compatibility issues with this one. Justin's use of database.schema.relname will work up until v14 (by throwing away the database part), then draw an error in v14, then (assuming we support the database portion in v15 onward) start working again.
3. If there are exactly two dots, treat it as db-schema-user. Accept
it if the dbname matches the current db, and otherwise say we can't
access the named db. If there are more than two dots, then (a) it's an
error as in (2) or (b) the extra ones become part of the regex-glob
thingies as in (2).
3a is a bit strange, when considered in the context of patterns. If db1, db2, and db3 all exist and each have a table foo.bar, and psql is connected to db1, how should the command \d db?.foo.bar behave? We have no problem with db1.foo.bar, but we do have problems with the other two. If the answer is to complain about the databases that are unconnected, consider what happens if the user writes this in a script when only db1 exists, and later the script stops working because somebody created database db2. Maybe that's not completely horrible, but surely it is less than ideal.
3b is what pg_amcheck does. It accepts database.schema.relname, and it will complain if no matching database/schema/relation can be found (unless --no-strict-names was given.)
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Wed, Oct 13, 2021 at 10:40 AM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
3a is a bit strange, when considered in the context of patterns. If db1, db2, and db3 all exist and each have a table foo.bar, and psql is connected to db1, how should the command \d db?.foo.bar behave? We have no problem with db1.foo.bar, but we do have problems with the other two. If the answer is to complain about the databases that are unconnected, consider what happens if the user writes this in a script when only db1 exists, and later the script stops working because somebody created database db2. Maybe that's not completely horrible, but surely it is less than ideal.
3b is what pg_amcheck does. It accepts database.schema.relname, and it will complain if no matching database/schema/relation can be found (unless --no-strict-names was given.)
Well, like I said, we can't treat a part that's purportedly a DB name
as a pattern, so when connected to db1, I presume the command \d
db?.foo.bar would have to behave just like \d
dskjlglsghdksgdjkshg.foo.bar. I suppose technically I'm wrong: db?
could be matched against the list of database names as a pattern, and
then we could complain only if it doesn't match exactly and only the
current DB. But I don't like adding a bunch of extra code to
accomplish nothing useful, so if we're going to match it all I think
it should just strcmp().
But I'm still not sure what the best thing to do overall is here.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
I would prefer if it errored if the datname didn't match the current database.
After all, it would've helped me to avoid making a confusing problem report.
How would you have felt if it had said something like:
error: argument to \d should be of the form
[schema-name-pattern.]relation-name-pattern
Would that have been better or worse for you than accepting a third
part of the pattern as a database name if and only if it matched the
current database name exactly?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Wed, Oct 13, 2021 at 12:46:27PM -0400, Robert Haas wrote:
On Tue, Oct 12, 2021 at 12:57 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
I would prefer if it errored if the datname didn't match the current database.
After all, it would've helped me to avoid making a confusing problem report.How would you have felt if it had said something like:
error: argument to \d should be of the form
[schema-name-pattern.]relation-name-patternWould that have been better or worse for you than accepting a third
part of the pattern as a database name if and only if it matched the
current database name exactly?
I don't normally type \d a.b.c. I think I copied it out of a log message and
pasted it, and didn't even really know or expect it to work without removing
the datname prefix. After it worked, I noticed a short while later when using
the pg14 client that it had stopped working.
It seems unfortunate if names from log messages qualified with datname were now
rejected. Like this one:
| automatic analyze of table "ts.child.cdrs_2021_10_12"...
--
Justin
On Wed, Oct 13, 2021 at 12:54 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
It seems unfortunate if names from log messages qualified with datname were now
rejected. Like this one:| automatic analyze of table "ts.child.cdrs_2021_10_12"...
That's a good argument, IMHO.
--
Robert Haas
EDB: http://www.enterprisedb.com
Greetings,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Wed, Oct 13, 2021 at 12:54 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
It seems unfortunate if names from log messages qualified with datname were now
rejected. Like this one:| automatic analyze of table "ts.child.cdrs_2021_10_12"...
That's a good argument, IMHO.
Agreed.
Thanks,
Stephen
On Oct 13, 2021, at 8:43 AM, Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Oct 13, 2021 at 10:40 AM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:3a is a bit strange, when considered in the context of patterns. If db1, db2, and db3 all exist and each have a table foo.bar, and psql is connected to db1, how should the command \d db?.foo.bar behave? We have no problem with db1.foo.bar, but we do have problems with the other two. If the answer is to complain about the databases that are unconnected, consider what happens if the user writes this in a script when only db1 exists, and later the script stops working because somebody created database db2. Maybe that's not completely horrible, but surely it is less than ideal.
3b is what pg_amcheck does. It accepts database.schema.relname, and it will complain if no matching database/schema/relation can be found (unless --no-strict-names was given.)
Well, like I said, we can't treat a part that's purportedly a DB name
as a pattern, so when connected to db1, I presume the command \d
db?.foo.bar would have to behave just like \d
dskjlglsghdksgdjkshg.foo.bar. I suppose technically I'm wrong: db?
could be matched against the list of database names as a pattern, and
then we could complain only if it doesn't match exactly and only the
current DB. But I don't like adding a bunch of extra code to
accomplish nothing useful, so if we're going to match it all I think
it should just strcmp().But I'm still not sure what the best thing to do overall is here.
The issue of name parsing impacts pg_dump and pg_dumpall, also. Consider what happens with:
pg_dump -t production.critical.secrets > secrets.dump
dropdb production
In v13, if your default database is "testing", and database "testing" has the same schemas and tables (but not data) as production, you are unhappy. You just dumped a copy of your test data and blew away the production data.
You could end up unhappy in v14, if database "testing" has a schema named "production" and a table that matches the pattern /^critical.secrets$/, but otherwise, you'll get an error from pg_dump, "pg_dump: error: no matching tables were found". Neither behavior seems correct.
The function where the processing occurs is processSQLNamePattern, which is called by pg_dump, pg_dumpall, and psql. All three callers expect processSQLNamePattern to append where-clauses to a buffer, not to execute any sql of its own. I propose that processSQLNamePattern return an error code if the pattern contains more than three parts, but otherwise insert the database portion into the buffer as a "pg_catalog.current_database() OPERATOR(pg_catalog.=) <database>", where <database> is a properly escaped representation of the database portion. Maybe someday we can change that to OPERATOR(pg_catalog.~), but for now we lack the sufficient logic for handling multiple matching database names. (The situation is different for pg_dumpall, as it's using the normal logic for matching a relation name, not for matching a database, and we'd still be fine matching that against a pattern.)
For psql and pg_dump, I'm tempted to restrict the database portion (if not quoted) to neither contain shell glob characters nor POSIX regex characters, and return an error code if any are found, so that the clients can raise an appropriate error to the user.
In psql, this proposal would result in no tables matching \d wrongdb.schema.table, which would differ from v13's behavior. You wouldn't get an error about having specified the wrong database. You'd just get no matching relations. \d ??db??.schema.table would complain about the db portion being a pattern. \d "??db??".schema.table would work, assuming you're connected to a database literally named ??db??
In pg_dumpall, --exclude-database=more.than.one.part would give an error about too many dotted parts rather than simply trying to exclude the last "part" and silently ignoring the prefix, which I think is what v13's pg_dumpall would do. --exclude-database=db?? would work to exclude four character database names beginning in "db".
In pg_dump, the -t wrongdb.schema.table would match nothing and give the familiar error "pg_dump: error: no matching tables were found". pg_dump -t too.many.dotted.names would give a different error about too many parts. pg_dump -t db??.foo.bar would give an error about the database needing to be a literal name rather than a pattern.
I don't like your proposal to use a strcmp() rather than a pg_catalog.= match, because it diverges from how the rest of the pattern is treated, including in how encoding settings might interact with the name, needing to be executed on the client side rather than in the server where the rest of the name resolution is happening.
Does this sound like a workable proposal?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Wed, Oct 13, 2021 at 4:43 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
The function where the processing occurs is processSQLNamePattern, which is called by pg_dump, pg_dumpall, and psql. All three callers expect processSQLNamePattern to append where-clauses to a buffer, not to execute any sql of its own. I propose that processSQLNamePattern return an error code if the pattern contains more than three parts, but otherwise insert the database portion into the buffer as a "pg_catalog.current_database() OPERATOR(pg_catalog.=) <database>", where <database> is a properly escaped representation of the database portion. Maybe someday we can change that to OPERATOR(pg_catalog.~), but for now we lack the sufficient logic for handling multiple matching database names. (The situation is different for pg_dumpall, as it's using the normal logic for matching a relation name, not for matching a database, and we'd still be fine matching that against a pattern.)
I agree with matching using OPERATOR(pg_catalog.=) but I think it
should be an error, not a silently-return-nothing case.
In pg_dumpall, --exclude-database=more.than.one.part would give an error about too many dotted parts rather than simply trying to exclude the last "part" and silently ignoring the prefix, which I think is what v13's pg_dumpall would do. --exclude-database=db?? would work to exclude four character database names beginning in "db".
Those things sound good.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Oct 13, 2021, at 1:43 PM, Mark Dilger <mark.dilger@enterprisedb.com> wrote:
The issue of name parsing impacts pg_dump and pg_dumpall, also. Consider what happens with:
pg_dump -t production.critical.secrets > secrets.dump
dropdb productionIn v13, if your default database is "testing", and database "testing" has the same schemas and tables (but not data) as production, you are unhappy. You just dumped a copy of your test data and blew away the production data.
You could end up unhappy in v14, if database "testing" has a schema named "production" and a table that matches the pattern /^critical.secrets$/, but otherwise, you'll get an error from pg_dump, "pg_dump: error: no matching tables were found". Neither behavior seems correct.
With the attached patch, this scenario results in a "cross-database references are not implemented" error.
The function where the processing occurs is processSQLNamePattern, which is called by pg_dump, pg_dumpall, and psql. All three callers expect processSQLNamePattern to append where-clauses to a buffer, not to execute any sql of its own. I propose that processSQLNamePattern return an error code if the pattern contains more than three parts, but otherwise insert the database portion into the buffer as a "pg_catalog.current_database() OPERATOR(pg_catalog.=) <database>", where <database> is a properly escaped representation of the database portion. Maybe someday we can change that to OPERATOR(pg_catalog.~), but for now we lack the sufficient logic for handling multiple matching database names. (The situation is different for pg_dumpall, as it's using the normal logic for matching a relation name, not for matching a database, and we'd still be fine matching that against a pattern.)
I ultimately went with your strcmp idea rather than OPERATOR(pg_catalog.=), as rejecting the database name as part of the query complicates the calling convention for no apparent benefit. I had been concerned about database names that were collation-wise equal but byte-wise unequal, but it seems we already treat those as distinct database names, so my concern was unnecessary. We already use strcmp on database names from frontend clients (fe_utils/parallel_slots.c, psql/prompt.c, pg_amcheck.c, pg_dump.c, pg_upgrade/relfilenode.c), from libpq (libpq/hba.c) and from the backend (commands/dbcommands.c, init/postinit.c).
I tried testing how this plays out by handing `createdb` the name é (U+00E9 "LATIN SMALL LETTER E WITH ACCUTE") and then again the name é (U+0065 "LATIN SMALL LETTER E" followed by U+0301 "COMBINING ACCUTE ACCENT".) That results in two distinct databases, not an error about a duplicate database name:
# select oid, datname, datdba, encoding, datcollate, datctype from pg_catalog.pg_database where datname IN ('é', 'é');
oid | datname | datdba | encoding | datcollate | datctype
-------+---------+--------+----------+-------------+-------------
37852 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
37855 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
(2 rows)
But that doesn't seem to prove much, as other tools in my locale don't treat those as equal either. (Testing with perl's "eq" operator, they compare as distinct.) I expected to find regression tests providing better coverage for this somewhere, but did not. Anybody know more about it?
For psql and pg_dump, I'm tempted to restrict the database portion (if not quoted) to neither contain shell glob characters nor POSIX regex characters, and return an error code if any are found, so that the clients can raise an appropriate error to the user.
With the patch, using pattern characters in an unquoted database portion results in a "database name must be literal" error. Using them in a quoted database name is allowed, but unless you are connected to a database that literally equals that name, you will get a "cross-database references are not implemented" error.
In psql, this proposal would result in no tables matching \d wrongdb.schema.table, which would differ from v13's behavior. You wouldn't get an error about having specified the wrong database. You'd just get no matching relations. \d ??db??.schema.table would complain about the db portion being a pattern. \d "??db??".schema.table would work, assuming you're connected to a database literally named ??db??
With the patch, psql will treat \d wrongdb.schema.table as a "cross-database references are not implemented" error.
In pg_dumpall, --exclude-database=more.than.one.part would give an error about too many dotted parts rather than simply trying to exclude the last "part" and silently ignoring the prefix, which I think is what v13's pg_dumpall would do. --exclude-database=db?? would work to exclude four character database names beginning in "db".
The patch implements this.
In pg_dump, the -t wrongdb.schema.table would match nothing and give the familiar error "pg_dump: error: no matching tables were found".
With the patch, pg_dump instead gives a "cross-database references are not implemented" error.
pg_dump -t too.many.dotted.names would give a different error about too many parts.
With the patch, pg_dump instead gives a "improper qualified name (too many dotted names)" error.
pg_dump -t db??.foo.bar would give an error about the database needing to be a literal name rather than a pattern.
With the patch, pg_dump gives a "database name must be literal" error. This is the only new error message in the patch, which puts a burden on translators, but I didn't see any existing message that would serve. Suggestions welcome.
I don't like your proposal to use a strcmp() rather than a pg_catalog.= match, because it diverges from how the rest of the pattern is treated, including in how encoding settings might interact with the name, needing to be executed on the client side rather than in the server where the rest of the name resolution is happening.
Recanted, as discussed above.
The patch only changes the behavior of pg_amcheck in that it now rejects patterns with too many parts. Using database patterns was and remains legal for this tool.
The patch changes nothing about reindexdb. That's a debatable design choice, but reindexdb doesn't use string_utils's processSQLNamePattern() function as the other tools do, nor does its documentation reference psql's #APP-PSQL-PATTERNS documentation. It's --schema option only takes literal names.
Attachments:
v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From 3136d4fe27052146782f28bf176898277306abc0 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Fri, 15 Oct 2021 09:07:15 -0700
Subject: [PATCH v1] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 510 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1010 insertions(+), 235 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 14e0a4dbe3..7c00a65966 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3605,14 +3605,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index d4a53c8e63..cb2945f9fb 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index e30c1cc546..800203886f 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ed8ed2f266..dc577a6d1d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -167,6 +167,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1341,10 +1344,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1388,10 +1407,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1435,10 +1461,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1481,6 +1514,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1496,9 +1533,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1519,6 +1568,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c29101704a..42820adcac 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1438,10 +1438,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c61d95e817..126be5f4e4 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3588,7 +3588,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3761,6 +3761,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ea4ca5c05c..8a1aa8ca88 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -45,6 +45,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -121,9 +127,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -189,9 +197,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -276,9 +286,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -639,9 +651,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -663,10 +677,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -804,11 +820,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -958,10 +976,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -986,10 +1006,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -1067,8 +1089,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1218,9 +1242,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1295,11 +1321,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1371,9 +1399,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1393,9 +1423,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/*
@@ -1421,9 +1453,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
}
/*
@@ -1450,9 +1484,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
}
/* Rule descriptions (ignore rules for views) */
@@ -1472,9 +1508,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1492,9 +1530,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1548,9 +1588,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3783,8 +3825,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
}
else
{
@@ -3798,8 +3842,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
" ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
"\nFROM pg_catalog.pg_user u\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "u.usename", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "u.usename", NULL, NULL,
+ NULL, 1))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3934,10 +3980,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -4152,9 +4201,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4367,9 +4418,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4447,8 +4500,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4537,9 +4592,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4611,9 +4668,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4678,8 +4737,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4770,10 +4831,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4879,17 +4942,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4986,9 +5053,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5044,10 +5113,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5105,9 +5176,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5146,9 +5219,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5363,9 +5438,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5434,9 +5511,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5494,9 +5573,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5536,9 +5617,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5724,8 +5807,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5806,8 +5891,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5867,8 +5954,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5944,9 +6033,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6000,10 +6091,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6049,10 +6142,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6134,6 +6229,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -6185,9 +6335,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6252,9 +6404,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6442,9 +6596,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6550,15 +6706,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6622,8 +6782,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6635,10 +6797,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6716,13 +6880,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6800,12 +6968,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 3efee4e7ee..c3ee191369 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -819,6 +819,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -827,16 +829,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -848,16 +855,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -870,25 +885,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -896,7 +911,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -904,16 +919,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -929,8 +944,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -965,32 +982,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1003,6 +1028,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1013,32 +1040,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1050,6 +1093,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1074,25 +1119,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index caafb97d29..d271cbc4bf 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -54,10 +54,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 930ce8597a..d770f980b3 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5278,3 +5278,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index e9d504baf2..042237f3ee 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1304,3 +1304,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
Mark Dilger <mark.dilger@enterprisedb.com> writes:
[ v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch ]
This needs a rebase over the recent renaming of our Perl test modules.
(Per the cfbot, so do several of your other pending patches.)
regards, tom lane
On Nov 3, 2021, at 12:07 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
[ v1-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch ]
This needs a rebase over the recent renaming of our Perl test modules.
(Per the cfbot, so do several of your other pending patches.)regards, tom lane
Thanks for calling my attention to it.
Attachments:
v2-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v2-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From 524d32cf08ff85b32a2cd596b867279395a2cb96 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 3 Nov 2021 13:42:30 -0700
Subject: [PATCH v2] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 510 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1010 insertions(+), 235 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 48248f750e..b2bcc7e296 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3605,14 +3605,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index d4a53c8e63..cb2945f9fb 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 513a18d671..5c38916a70 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b9635a95b6..e143ee4efb 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -167,6 +167,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1341,10 +1344,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1388,10 +1407,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1435,10 +1461,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1481,6 +1514,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1496,9 +1533,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1519,6 +1568,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c29101704a..42820adcac 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1438,10 +1438,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index d293f52b05..ddc103ea9c 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3637,7 +3637,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3810,6 +3810,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412e..f13fc356f7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -45,6 +45,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -121,9 +127,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -189,9 +197,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -276,9 +286,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -639,9 +651,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -663,10 +677,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -804,11 +820,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -958,10 +976,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -986,10 +1006,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -1067,8 +1089,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1218,9 +1242,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1295,11 +1321,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1371,9 +1399,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1393,9 +1423,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/*
@@ -1421,9 +1453,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
}
/*
@@ -1450,9 +1484,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
}
/* Rule descriptions (ignore rules for views) */
@@ -1472,9 +1508,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1492,9 +1530,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1548,9 +1588,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3806,8 +3848,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
}
else
{
@@ -3821,8 +3865,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
" ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
"\nFROM pg_catalog.pg_user u\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "u.usename", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "u.usename", NULL, NULL,
+ NULL, 1))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3957,10 +4003,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -4175,9 +4224,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4390,9 +4441,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4470,8 +4523,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4560,9 +4615,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4634,9 +4691,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4701,8 +4760,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4793,10 +4854,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4902,17 +4965,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -5009,9 +5076,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5069,10 +5138,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5193,9 +5264,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5234,9 +5307,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5451,9 +5526,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5522,9 +5599,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5582,9 +5661,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5624,9 +5705,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5812,8 +5895,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5894,8 +5979,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5955,8 +6042,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6032,9 +6121,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6088,10 +6179,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6137,10 +6230,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6222,6 +6317,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -6273,9 +6423,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6378,9 +6530,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6562,9 +6716,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6670,15 +6826,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6742,8 +6902,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6755,10 +6917,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6836,13 +7000,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6920,12 +7088,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 3efee4e7ee..c3ee191369 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -819,6 +819,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -827,16 +829,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -848,16 +855,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -870,25 +885,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -896,7 +911,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -904,16 +919,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -929,8 +944,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -965,32 +982,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1003,6 +1028,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1013,32 +1040,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1050,6 +1093,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1074,25 +1119,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index caafb97d29..d271cbc4bf 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -54,10 +54,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 930ce8597a..d770f980b3 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5278,3 +5278,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index e9d504baf2..042237f3ee 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1304,3 +1304,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
On Wed, Oct 13, 2021 at 09:24:53AM -0400, Robert Haas wrote:
Splitting the pattern on all the dots and throwing away any additional
leftmost fields is a bug, ...I also agree with you right up to here.
and when you stop doing that, passing additional dots through to the POSIX
regular expression for processing is the most natural thing to do. This
is, in fact, how v14 works. It is a bit debatable whether treating the
first dot as a separator and the additional dots as stuff to be passed
through is the right thing, so we could call the v14 behavior a
mis-feature, but it's not as clearcut as the discussion upthread suggested.
Reverting to v13 behavior seems wrong, but I'm now uncertain how to
proceed.But not this part, or at least not entirely.
If we pass the dots through to the POSIX regular expression, we can
only do that either for the table name or the schema name, not both -
either the first or last dot must mark the boundary between the two.
That means that you can't use all the same regexy things for one as
you can for the other, which is a strange system. I knew that your
patch made it do that, and I committed it that way because I didn't
think it really mattered, and also because the whole system is already
pretty strange, so what's one more bit of strangeness?
Rather than trying to guess at the meaning of each '.' based on the total
string. I wonder, if we could for v15 require '.' to be spelled in longer way
if it needs to be treated as part of the regex.
Perhaps requiring something like '(.)' be used rather than a bare '.'
might be good enough and documenting otherwise it's really a separator?
I suppose we could also invent a non-standard class as a stand in like
'[::any::]', but that seems kinda weird.
I think it might be possible to give better error messages long term
if we knew what '.' should mean without looking at the whole thing.
Garick
On Nov 4, 2021, at 6:37 AM, Hamlin, Garick L <ghamlin@isc.upenn.edu> wrote:
If we pass the dots through to the POSIX regular expression, we can
only do that either for the table name or the schema name, not both -
either the first or last dot must mark the boundary between the two.
That means that you can't use all the same regexy things for one as
you can for the other, which is a strange system. I knew that your
patch made it do that, and I committed it that way because I didn't
think it really mattered, and also because the whole system is already
pretty strange, so what's one more bit of strangeness?Rather than trying to guess at the meaning of each '.' based on the total
string. I wonder, if we could for v15 require '.' to be spelled in longer way
if it needs to be treated as part of the regex.
We're trying to fix an edge case, not change how the basic case works. Most users are accustomed to using patterns from within psql like:
\dt myschema.mytable
Whatever patch we accept must not break these totally normal and currently working cases.
Perhaps requiring something like '(.)' be used rather than a bare '.'
might be good enough and documenting otherwise it's really a separator?
I suppose we could also invent a non-standard class as a stand in like
'[::any::]', but that seems kinda weird.
If I understand you, that would require the above example to be written as:
\dt myschema(.)mytable
which nobody expects to have to do, and which would be a very significant breaking change in v15. I can't see anything like that being accepted.
I think it might be possible to give better error messages long term
if we knew what '.' should mean without looking at the whole thing.
You quote a portion of an email from Robert. After that email, there were several more, and a new patch. The commit message of the new patch explains what it does. I wonder if you'd review that message, quoted here, or even better, review the entire patch. Does this seem like an ok fix to you?
Subject: [PATCH v2] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
/messages/by-id/20211013165426.GD27491@telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 2021-Oct-20, Mark Dilger wrote:
I tried testing how this plays out by handing `createdb` the name é
(U+00E9 "LATIN SMALL LETTER E WITH ACCUTE") and then again the name é
(U+0065 "LATIN SMALL LETTER E" followed by U+0301 "COMBINING ACCUTE
ACCENT".) That results in two distinct databases, not an error about
a duplicate database name:# select oid, datname, datdba, encoding, datcollate, datctype from pg_catalog.pg_database where datname IN ('é', 'é');
oid | datname | datdba | encoding | datcollate | datctype
-------+---------+--------+----------+-------------+-------------
37852 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
37855 | é | 10 | 6 | en_US.UTF-8 | en_US.UTF-8
(2 rows)But that doesn't seem to prove much, as other tools in my locale don't
treat those as equal either. (Testing with perl's "eq" operator, they
compare as distinct.) I expected to find regression tests providing
better coverage for this somewhere, but did not. Anybody know more
about it?
I think it would appropriate to normalize identifiers that are going to
be stored in catalogs. As presented, this is a bit ridiculous and I see
no reason to continue to support it.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Ed is the standard text editor."
http://groups.google.com/group/alt.religion.emacs/msg/8d94ddab6a9b0ad3
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I think it would appropriate to normalize identifiers that are going to
be stored in catalogs. As presented, this is a bit ridiculous and I see
no reason to continue to support it.
If we had any sort of convention about the encoding of identifiers stored
in shared catalogs, maybe we could do something about that. But we don't,
so any change is inevitably going to break someone's use-case.
In any case, that seems quite orthogonal to the question of how to treat
names with too many dots in them. Considering we are three days out from
freezing 14.1, I think it is time to stop the meandering discussion and
fix it. And by "fix", I mean revert to the pre-14 behavior.
regards, tom lane
On Fri, Nov 5, 2021 at 9:59 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
In any case, that seems quite orthogonal to the question of how to treat
names with too many dots in them. Considering we are three days out from
freezing 14.1, I think it is time to stop the meandering discussion and
fix it. And by "fix", I mean revert to the pre-14 behavior.
I do not think that there is consensus on that proposal.
And FWIW, I still oppose it. It's debatable whether this even
qualifies as a bug in the first place, and even more debatable whether
accepting and ignoring arbitrary garbage is the right solution.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Nov 5, 2021, at 6:59 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
I think it would appropriate to normalize identifiers that are going to
be stored in catalogs. As presented, this is a bit ridiculous and I see
no reason to continue to support it.If we had any sort of convention about the encoding of identifiers stored
in shared catalogs, maybe we could do something about that. But we don't,
so any change is inevitably going to break someone's use-case.
I only started the discussion about normalization to demonstrate that existing behavior does not require it.
In any case, that seems quite orthogonal to the question of how to treat
names with too many dots in them.
Agreed.
Considering we are three days out from
freezing 14.1, I think it is time to stop the meandering discussion and
fix it.
Agreed.
And by "fix", I mean revert to the pre-14 behavior.
That's one solution. The patch I posted on October 20, and rebased two days ago, has not received any negative feedback. If you want to revert to pre-14 behavior for 14.1, do you oppose the patch going in for v15? (I'm not taking a position here, just asking what you'd prefer.)
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Rebased patch attached:
Attachments:
v3-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v3-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From edad6436177c0110e9f87c300e5e8ea56cb086a6 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 21 Dec 2021 08:17:37 -0800
Subject: [PATCH v3] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1006 insertions(+), 233 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index ae38d3dcc3..03c33bde69 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3627,14 +3627,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index d4a53c8e63..cb2945f9fb 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 513a18d671..5c38916a70 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index b52f3ccda2..603202cbcc 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -171,6 +171,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1308,10 +1311,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1355,10 +1374,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1402,10 +1428,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1448,6 +1481,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1463,9 +1500,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1486,6 +1535,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9ff0c091a9..9caa5f64ad 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1215,10 +1215,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fd01651d9c..b03496c532 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3704,7 +3704,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3877,6 +3877,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index c28788e84f..4ae51fd8df 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -916,8 +938,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1066,9 +1090,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1133,11 +1159,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1209,9 +1237,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1231,9 +1261,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1253,9 +1285,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1275,9 +1309,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1296,9 +1332,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1316,9 +1354,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1372,9 +1412,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3493,8 +3535,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3617,10 +3661,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3813,9 +3860,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4028,9 +4077,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4103,8 +4154,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4186,9 +4239,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4260,9 +4315,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4337,8 +4394,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4429,10 +4488,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4531,17 +4592,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4628,9 +4693,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4688,10 +4755,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4802,9 +4871,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4843,9 +4914,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5050,9 +5123,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5111,9 +5186,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5161,9 +5238,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5203,9 +5282,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5375,8 +5456,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5447,8 +5530,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5498,8 +5583,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5565,9 +5652,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5611,10 +5700,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5650,10 +5741,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5735,6 +5828,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5786,9 +5934,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5891,9 +6041,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6075,9 +6227,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6178,15 +6332,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6250,8 +6408,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6263,10 +6423,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6344,13 +6506,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6428,12 +6594,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 81e623602e..dc010dc4c6 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +892,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +918,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +948,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +974,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +982,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1007,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1028,32 +1045,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1091,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1103,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1156,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1182,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index e12e61cddb..095b8729ad 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..bcf32f7d83 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d4e4fdbbb7..cd065b4a7f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
Hi,
On Tue, Dec 21, 2021 at 10:58:39AM -0800, Mark Dilger wrote:
Rebased patch attached:
This version doesn't apply anymore:
http://cfbot.cputube.org/patch_36_3367.log
=== Applying patches on top of PostgreSQL commit ID 5513dc6a304d8bda114004a3b906cc6fde5d6274 ===
=== applying patch ./v3-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch
[...]
1 out of 52 hunks FAILED -- saving rejects to file src/bin/psql/describe.c.rej
Could you send a rebased version? In the meantime I will switch the cf entry
to Waiting on Author.
On Jan 15, 2022, at 12:28 AM, Julien Rouhaud <rjuju123@gmail.com> wrote:
Could you send a rebased version?
Yes. Here it is:
Attachments:
v4-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v4-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From cc7bbe52a1e0c2c522a5dd91c1564d5c764bb89e Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 17 Jan 2022 10:04:30 -0800
Subject: [PATCH v4] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 113 ++++++
11 files changed, 1006 insertions(+), 233 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1ab200a4ad..32dbb8cdef 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3633,14 +3633,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..80fffb1d2e 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 8d7bd7ebcc..ddb77ba9ee 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c5f231118b..7e74231293 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1387,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1441,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1494,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1513,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1548,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 94852e7cdb..c800ccf0c7 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1220,10 +1220,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 39fa1952e7..1fc3652dba 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3716,7 +3716,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3889,6 +3889,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8587b19160..4ac84e174c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -916,8 +938,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1066,9 +1090,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1133,11 +1159,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1209,9 +1237,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1231,9 +1261,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1253,9 +1285,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1275,9 +1309,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1296,9 +1332,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1316,9 +1354,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1372,9 +1412,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3501,8 +3543,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3625,10 +3669,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3821,9 +3868,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4036,9 +4085,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4111,8 +4162,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4194,9 +4247,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4268,9 +4323,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4345,8 +4402,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4437,10 +4496,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4539,17 +4600,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4636,9 +4701,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4696,10 +4763,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4810,9 +4879,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4851,9 +4922,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5058,9 +5131,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5119,9 +5194,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5169,9 +5246,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5211,9 +5290,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5383,8 +5464,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5455,8 +5538,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5506,8 +5591,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5573,9 +5660,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5619,10 +5708,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5658,10 +5749,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5743,6 +5836,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5794,9 +5942,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5899,9 +6049,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6083,9 +6235,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6186,15 +6340,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6258,8 +6416,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6271,10 +6431,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6352,13 +6514,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6436,12 +6602,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..5e7da77147 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +892,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +918,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +948,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +974,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +982,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1007,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1028,32 +1045,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1091,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1103,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1156,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1182,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..30f4c9caf7 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..bcf32f7d83 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index d4e4fdbbb7..cd065b4a7f 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,116 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
On Mon, Jan 17, 2022 at 1:06 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
On Jan 15, 2022, at 12:28 AM, Julien Rouhaud <rjuju123@gmail.com> wrote:
Could you send a rebased version?Yes. Here it is:
This is not a full review, but I just noticed that:
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
But then:
+ Assert(dotcnt != NULL);
On a related note, it's unclear why you've added three new arguments
to processSQLNamePattern() but only one of them gets a mention in the
function header comment.
It's also pretty clear that the behavior of patternToSQLRegex() is
changing, but the function header comments are not.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Jan 17, 2022, at 1:54 PM, Robert Haas <robertmhaas@gmail.com> wrote:
+ * dotcnt: how many separators were parsed from the pattern, by reference. + * Can be NULL.But then:
+ Assert(dotcnt != NULL);
Removed the "Can be NULL" part, as that use case doesn't make sense. The caller should always care whether the number of dots was greater than they are prepared to handle.
On a related note, it's unclear why you've added three new arguments
to processSQLNamePattern() but only one of them gets a mention in the
function header comment.
Updated the header comments to include all parameters.
It's also pretty clear that the behavior of patternToSQLRegex() is
changing, but the function header comments are not.
Updated the header comments for this, too.
Also, rebased as necessary:
Attachments:
v5-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v5-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From a9c2c0740e4f8bc58e91d3d37883752f6107b72d Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 26 Jan 2022 08:12:14 -0800
Subject: [PATCH v5] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 40 ++-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 61 +++-
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 1005 insertions(+), 233 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1ab200a4ad..32dbb8cdef 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3633,14 +3633,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..80fffb1d2e 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 8d7bd7ebcc..ddb77ba9ee 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 76;
+use Test::More tests => 82;
# Test set-up
my ($node, $port);
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e3ddf19959..88ca55a87a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1387,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1441,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1494,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1513,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1548,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 10383c713f..8eb34bce12 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1223,10 +1223,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 39fa1952e7..1fc3652dba 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3716,7 +3716,7 @@ $node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 27;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3889,6 +3889,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 346cd92793..0c112044f4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -916,8 +938,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1066,9 +1090,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1133,11 +1159,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1209,9 +1237,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1231,9 +1261,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1253,9 +1285,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1275,9 +1309,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1296,9 +1332,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1316,9 +1354,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1372,9 +1412,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3523,8 +3565,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3647,10 +3691,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3843,9 +3890,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4058,9 +4107,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4133,8 +4184,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4216,9 +4269,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4290,9 +4345,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4367,8 +4424,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4459,10 +4518,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4561,17 +4622,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4658,9 +4723,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4718,10 +4785,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4832,9 +4901,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4873,9 +4944,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5080,9 +5153,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5141,9 +5216,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5191,9 +5268,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5233,9 +5312,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5405,8 +5486,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5477,8 +5560,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5528,8 +5613,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5595,9 +5682,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5641,10 +5730,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5680,10 +5771,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5765,6 +5858,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5816,9 +5964,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5921,9 +6071,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6105,9 +6257,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6208,15 +6362,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6280,8 +6438,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6293,10 +6453,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6374,13 +6536,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6458,12 +6624,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..5e7da77147 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +892,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +918,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +948,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +974,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +982,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1007,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1028,32 +1045,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1091,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1103,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1156,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1182,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..30f4c9caf7 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..bcf32f7d83 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 0f5287f77b..5edbb0f0da 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,115 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.21.1 (Apple Git-122.3)
Continuing my pass through the "bug fixes" section of the CommitFest,
I came upon this patch, which is contested. Here is my attempt to
summarize where things stand. As I understand it:
- Tom wants to revert to the previous behavior of accepting arbitrary
garbage, so that \d slkgjskld.jgdsjhgjklsdhg.saklasgh.foo.bar means \d
foo.bar.
- I want \d mydb.foo.bar to mean \d foo.bar if the dbname is mydb and
report an error otherwise; anything with dots>2 is also an error in my
view.
- Peter Geoghegan agrees with Tom.
- Stephen Frost agrees with me.
- Vik Fearing also agrees with me.
- Justin Pryzby, who originally discovered the problem, prefers the
same behavior that I prefer long-term, but thinks Tom's behavior is
better than doing nothing.
- Mark Dilger, Isaac Moreland, Garick Hamlin, Alvaro Herrera, and
Julien Rouhaud have commented on the thread but have not endorsed
either of these dueling proposals.
By my count, that's probably a vote of 4-2 in view of the preferred
solution, but it depends on whether you could Justin's vote as +1 for
my preferred solution or maybe +0.75 or +0.50 or something. At any
rate, it's close.
If anyone else would like to take a position, please do so in the next
few days. If there are no more votes, I'm going to proceed with trying
to fix up Mark's patch implementing my preferred solution and getting
it committed.
Thanks,
...Robert
On Mar 15, 2022, at 12:27 PM, Robert Haas <robertmhaas@gmail.com> wrote:
- Justin Pryzby, who originally discovered the problem, prefers the
same behavior that I prefer long-term, but thinks Tom's behavior is
better than doing nothing.
- Mark Dilger, Isaac Moreland, Garick Hamlin, Alvaro Herrera, and
Julien Rouhaud have commented on the thread but have not endorsed
either of these dueling proposals.
I vote in favor of committing the patch, though I'd also say it's not super important to me.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Wed, Oct 13, 2021 at 11:54:26AM -0500, Justin Pryzby wrote:
It seems unfortunate if names from log messages qualified with datname were now
rejected. Like this one:| automatic analyze of table "ts.child.cdrs_2021_10_12"...
Mark mentioned this "log message" use case in his proposed commit message, but
I wanted to mention what seems like a more important parallel:
postgres=# SELECT 'postgres.public.postgres_log'::regclass;
regclass | postgres_log
postgres=# SELECT 'not.postgres.public.postgres_log'::regclass;
ERROR: improper relation name (too many dotted names): not.postgres.public.postgres_log
^
postgres=# SELECT 'not.public.postgres_log'::regclass;
ERROR: cross-database references are not implemented: "not.public.postgres_log"
I think Mark used this as the model behavior for \d for this patch, which
sounds right. Since the "two dot" case wasn't fixed in 14.1 nor 2, it seems
better to implement the ultimate, intended behavior now, rather than trying to
exactly match what old versions did. I'm of the understanding that's what
Mark's patch does, so +1 from me.
I don't know how someone upgrading from an old version would know about the
change, though (rejecting junk prefixes rather than ignoring them). *If* it
were important, it seems like it'd need to be added to the 14.0 release notes.
On Tue, Mar 15, 2022 at 12:31 PM Mark Dilger <mark.dilger@enterprisedb.com>
wrote:
On Mar 15, 2022, at 12:27 PM, Robert Haas <robertmhaas@gmail.com> wrote:
- Justin Pryzby, who originally discovered the problem, prefers the
same behavior that I prefer long-term, but thinks Tom's behavior is
better than doing nothing.
- Mark Dilger, Isaac Moreland, Garick Hamlin, Alvaro Herrera, and
Julien Rouhaud have commented on the thread but have not endorsed
either of these dueling proposals.I vote in favor of committing the patch, though I'd also say it's not
super important to me.
I'm on board with leaving the v14 change in place - fixing the bug so that
a matching database name is accepted (the whole copy-from-logs argument is
quite compelling). I'm not too concerned about psql, since \d is mainly
used interactively, and since the change will result in errors in
pg_dump/pg_restore the usual due diligence for upgrading should handle the
necessary tweaks should the case arise where bogus/ignore stuff is present.
David J.
On 2022-01-26 09:04:15 -0800, Mark Dilger wrote:
Also, rebased as necessary:
Needs another one: http://cfbot.cputube.org/patch_37_3367.log
Marked as waiting-on-author.
Greetings,
Andres Freund
Show quoted text
On Mar 21, 2022, at 6:12 PM, Andres Freund <andres@anarazel.de> wrote:
Needs another one: http://cfbot.cputube.org/patch_37_3367.log
Marked as waiting-on-author.
Attachments:
v6-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v6-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From 91eac9ba98d8e68cbbcc31a61107f21294208a06 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 21 Mar 2022 18:30:37 -0700
Subject: [PATCH v6] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 38 +-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 59 ++++
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 1003 insertions(+), 231 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index caabb06c53..2139673fbd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3633,14 +3633,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..80fffb1d2e 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..75d1ebc6fb 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 725cd2e4eb..7376fdf9cd 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1387,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1441,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1494,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1513,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1548,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..6e16371314 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1226,10 +1226,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fd1052e5db..48b42bda1f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3848,6 +3848,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 991bfc1546..755a44f09e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3557,8 +3599,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3681,10 +3725,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3877,9 +3924,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4092,9 +4141,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4167,8 +4218,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4250,9 +4303,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4324,9 +4379,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4401,8 +4458,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4493,10 +4552,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4595,17 +4656,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4701,9 +4766,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4761,10 +4828,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4875,9 +4944,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4916,9 +4987,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5123,9 +5196,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5184,9 +5259,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5234,9 +5311,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5276,9 +5355,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5448,8 +5529,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5520,8 +5603,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5571,8 +5656,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5638,9 +5725,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5684,10 +5773,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5723,10 +5814,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5808,6 +5901,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5859,9 +6007,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5968,9 +6118,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6161,9 +6313,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6264,15 +6418,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6336,8 +6494,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6349,10 +6509,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6430,13 +6592,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6514,12 +6680,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..5e7da77147 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +892,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +918,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +948,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +974,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +982,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1007,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1028,32 +1045,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1091,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1103,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1156,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1182,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..30f4c9caf7 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..bcf32f7d83 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 0f5287f77b..5edbb0f0da 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,115 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.35.1
On Mon, Mar 21, 2022 at 9:32 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
[ new patch version ]
This patch adds three new arguments to processSQLNamePattern() and
documents one of them. It adds three new parameters to
patternToSQLRegex() as well, and documents none of them. I think that
the text of the comment might need some updating too, in particular
the sentence "Additional dots in the name portion are not treated as
special."
There are no comments explaining the left_is_literal stuff. It appears
that your intention here is that if the pattern string supplied by the
user contains any of *?|+()[]{}.^\ not surrounded by double-quotes, we
signal the caller. Some callers then use this to issue a complaint
that the database name must be a literal. To me, this behavior doesn't
really make sense. If something is a literal, that means we're not
going to interpret the special characters that it contains. Here, we
are interpreting the special characters just so we can complain that
they exist. It seems to me that a simpler solution would be to not
interpret them at all. I attach a patch showing what I mean by that.
It just rips out the dbname_is_literal stuff in favor of doing nothing
at all. To put the whole thing another way, if the user types "\d
}.public.ft", your code wants to complain about the fact that the user
is trying to use regular expression characters in a place where they
are not allowed to do that. I argue that we should instead just be
comparing "}" against the database name and see whether it happens to
match.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
0002-Remove-dbname_is_literal-stuff.patchapplication/octet-stream; name=0002-Remove-dbname_is_literal-stuff.patchDownload
From 2bcc43b6f664d0c47cab6d9f5814ea1af3ae8937 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 22 Mar 2022 13:55:15 -0400
Subject: [PATCH 2/2] Remove dbname_is_literal stuff.
---
src/bin/pg_amcheck/pg_amcheck.c | 6 ++---
src/bin/pg_dump/pg_dump.c | 20 +++------------
src/bin/pg_dump/pg_dumpall.c | 2 +-
src/bin/pg_dump/t/002_pg_dump.pl | 6 -----
src/bin/psql/describe.c | 21 +++++----------
src/fe_utils/string_utils.c | 40 ++++++++---------------------
src/include/fe_utils/string_utils.h | 6 ++---
src/test/regress/expected/psql.out | 34 ++++++++++++------------
8 files changed, 43 insertions(+), 92 deletions(-)
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 80fffb1d2e..522dccf15a 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1339,7 +1339,7 @@ append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding
initPQExpBuffer(&buf);
patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
- &dotcnt, NULL);
+ &dotcnt);
if (dotcnt > 0)
{
pg_log_error("improper qualified name (too many dotted names): %s", pattern);
@@ -1372,7 +1372,7 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
initPQExpBuffer(&nspbuf);
patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
- &dotcnt, NULL);
+ &dotcnt);
if (dotcnt > 1)
{
pg_log_error("improper qualified name (too many dotted names): %s", pattern);
@@ -1417,7 +1417,7 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
initPQExpBuffer(&relbuf);
patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
- false, &dotcnt, NULL);
+ false, &dotcnt);
if (dotcnt > 2)
{
pg_log_error("improper relation name (too many dotted names): %s", pattern);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 998601ad0c..f93afc9427 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1326,23 +1326,18 @@ expand_schema_name_patterns(Archive *fout,
{
PQExpBufferData dbbuf;
int dotcnt;
- bool dbname_is_literal;
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
false, NULL, "n.nspname", NULL, NULL, &dbbuf,
- &dotcnt, &dbname_is_literal);
+ &dotcnt);
if (dotcnt > 1)
fatal("improper qualified name (too many dotted names): %s",
cell->val);
else if (dotcnt == 1)
- {
- if (!dbname_is_literal)
- fatal("database name must be literal: %s", cell->val);
prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
- }
termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1388,13 +1383,12 @@ expand_extension_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
int dotcnt;
- bool dbname_is_literal;
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
false, NULL, "e.extname", NULL, NULL, NULL,
- &dotcnt, &dbname_is_literal);
+ &dotcnt);
if (dotcnt > 0)
fatal("improper qualified name (too many dotted names): %s",
cell->val);
@@ -1442,13 +1436,12 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
int dotcnt;
- bool dbname_is_literal;
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
false, NULL, "s.srvname", NULL, NULL, NULL,
- &dotcnt, &dbname_is_literal);
+ &dotcnt);
if (dotcnt > 0)
fatal("improper qualified name (too many dotted names): %s",
cell->val);
@@ -1496,7 +1489,6 @@ expand_table_name_patterns(Archive *fout,
{
PQExpBufferData dbbuf;
int dotcnt;
- bool dbname_is_literal;
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
@@ -1517,16 +1509,12 @@ expand_table_name_patterns(Archive *fout,
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
- &dotcnt, &dbname_is_literal);
+ &dotcnt);
if (dotcnt > 2)
fatal("improper relation name (too many dotted names): %s",
cell->val);
else if (dotcnt == 2)
- {
- if (!dbname_is_literal)
- fatal("database name must be literal: %s", cell->val);
prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
- }
termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 6e16371314..c35fb05ee5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1232,7 +1232,7 @@ expand_dbname_patterns(PGconn *conn,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
false, NULL, "datname", NULL, NULL, NULL,
- &dotcnt, NULL);
+ &dotcnt);
if (dotcnt > 0)
{
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 48b42bda1f..8c52e11ee3 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3901,12 +3901,6 @@ $node->command_fails_like(
'pg_dump: option --table rejects cross-database three part table names'
);
-$node->command_fails_like(
- [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
- qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
- 'pg_dump: option --table rejects non-literal database name'
-);
-
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index ee585084b1..a34f1713a1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -5920,14 +5920,12 @@ validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
{
PQExpBufferData dbbuf;
int dotcnt;
- bool dbname_is_literal;
bool added;
initPQExpBuffer(&dbbuf);
added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
schemavar, namevar, altnamevar,
- visibilityrule, &dbbuf, &dotcnt,
- &dbname_is_literal);
+ visibilityrule, &dbbuf, &dotcnt);
if (added_clause != NULL)
*added_clause = added;
@@ -5939,19 +5937,12 @@ validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
return false;
}
- if (maxparts > 1 && dotcnt == maxparts-1)
+ if (maxparts > 1 && dotcnt == maxparts-1 &&
+ ((PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)))
{
- if (!dbname_is_literal)
- {
- pg_log_error("database name must be literal: %s", pattern);
- return false;
- }
- else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
- {
- pg_log_error("cross-database references are not implemented: %s",
- pattern);
- return false;
- }
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
}
return true;
}
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 5e7da77147..5122f8f8e5 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -893,7 +893,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
const char *altnamevar, const char *visibilityrule,
- PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
+ PQExpBuffer db, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
@@ -935,7 +935,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* using appendStringLiteralConn().
*/
patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
- force_escape, true, dotcnt, dbname_is_literal);
+ force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -1046,8 +1046,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
PQExpBuffer namebuf, const char *pattern, bool force_escape,
- bool want_literal_dbname, int *dotcnt,
- bool *dbname_is_literal)
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
PQExpBufferData left_literal;
@@ -1055,8 +1054,7 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
PQExpBuffer maxbuf;
int i;
bool inquotes;
- bool left,
- left_is_literal;
+ bool left;
const char *cp;
Assert(pattern != NULL);
@@ -1078,7 +1076,6 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
left = true;
if (want_literal_dbname)
initPQExpBuffer(&left_literal);
- left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1111,23 +1108,15 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
- if (left)
- {
- if (want_literal_dbname)
- appendPQExpBufferChar(&left_literal, '*');
- left_is_literal = false;
- }
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
- if (left)
- {
- if (want_literal_dbname)
- appendPQExpBufferChar(&left_literal, '?');
- left_is_literal = false;
- }
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
else if (!inquotes && ch == '.')
@@ -1183,13 +1172,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
i = PQmblenBounded(cp, encoding);
while (i--)
{
- if (left)
- {
- if (want_literal_dbname)
- appendPQExpBufferChar(&left_literal, *cp);
- if (!inquotes && strchr("|+()[]{}.^\\", *cp))
- left_is_literal = false;
- }
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
}
}
@@ -1217,9 +1201,5 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
termPQExpBuffer(curbuf);
- if (dbname_is_literal)
- *dbname_is_literal = left_is_literal;
}
- else if (dbname_is_literal)
- *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 30f4c9caf7..47287c9807 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -56,13 +56,11 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
const char *altnamevar, const char *visibilityrule,
- PQExpBuffer db, int *dotcnt,
- bool *dbname_is_literal);
+ PQExpBuffer db, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
const char *pattern, bool force_escape,
- bool want_literal_dbname, int *dotcnt,
- bool *dbname_is_literal);
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index bcf32f7d83..64d8cba5a2 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5298,13 +5298,13 @@ improper qualified name (too many dotted names): nonesuch.heap
\dt host.regression.pg_catalog.pg_class
improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
\dt |.pg_catalog.pg_class
-database name must be literal: |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
\dt nonesuch.pg_catalog.pg_class
cross-database references are not implemented: nonesuch.pg_catalog.pg_class
\da host.regression.pg_catalog.sum
improper qualified name (too many dotted names): host.regression.pg_catalog.sum
\da +.pg_catalog.sum
-database name must be literal: +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
\da nonesuch.pg_catalog.sum
cross-database references are not implemented: nonesuch.pg_catalog.sum
\dAc nonesuch.brin
@@ -5330,37 +5330,37 @@ improper qualified name (too many dotted names): regression.pg_default
\dc host.regression.public.conversion
improper qualified name (too many dotted names): host.regression.public.conversion
\dc (.public.conversion
-database name must be literal: (.public.conversion
+cross-database references are not implemented: (.public.conversion
\dc nonesuch.public.conversion
cross-database references are not implemented: nonesuch.public.conversion
\dC host.regression.pg_catalog.int8
improper qualified name (too many dotted names): host.regression.pg_catalog.int8
\dC ).pg_catalog.int8
-database name must be literal: ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
\dC nonesuch.pg_catalog.int8
cross-database references are not implemented: nonesuch.pg_catalog.int8
\dd host.regression.pg_catalog.pg_class
improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
\dd [.pg_catalog.pg_class
-database name must be literal: [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
\dd nonesuch.pg_catalog.pg_class
cross-database references are not implemented: nonesuch.pg_catalog.pg_class
\dD host.regression.public.gtestdomain1
improper qualified name (too many dotted names): host.regression.public.gtestdomain1
\dD ].public.gtestdomain1
-database name must be literal: ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
\dD nonesuch.public.gtestdomain1
cross-database references are not implemented: nonesuch.public.gtestdomain1
\ddp host.regression.pg_catalog.pg_class
improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
\ddp {.pg_catalog.pg_class
-database name must be literal: {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
\ddp nonesuch.pg_catalog.pg_class
cross-database references are not implemented: nonesuch.pg_catalog.pg_class
\dE host.regression.public.ft
improper qualified name (too many dotted names): host.regression.public.ft
\dE }.public.ft
-database name must be literal: }.public.ft
+cross-database references are not implemented: }.public.ft
\dE nonesuch.public.ft
cross-database references are not implemented: nonesuch.public.ft
\di host.regression.public.tenk1_hundred
@@ -5372,25 +5372,25 @@ cross-database references are not implemented: nonesuch.public.tenk1_hundred
\dm host.regression.public.mvtest_bb
improper qualified name (too many dotted names): host.regression.public.mvtest_bb
\dm ^.public.mvtest_bb
-database name must be literal: ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
\dm nonesuch.public.mvtest_bb
cross-database references are not implemented: nonesuch.public.mvtest_bb
\ds host.regression.public.check_seq
improper qualified name (too many dotted names): host.regression.public.check_seq
\ds regression|mydb.public.check_seq
-database name must be literal: regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
\ds nonesuch.public.check_seq
cross-database references are not implemented: nonesuch.public.check_seq
\dt host.regression.public.b_star
improper qualified name (too many dotted names): host.regression.public.b_star
\dt regres+ion.public.b_star
-database name must be literal: regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
\dt nonesuch.public.b_star
cross-database references are not implemented: nonesuch.public.b_star
\dv host.regression.public.shoe
improper qualified name (too many dotted names): host.regression.public.shoe
\dv regress(ion).public.shoe
-database name must be literal: regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
\dv nonesuch.public.shoe
cross-database references are not implemented: nonesuch.public.shoe
\des nonesuch.server
@@ -5412,25 +5412,25 @@ improper qualified name (too many dotted names): regression.fdw
\df host.regression.public.namelen
improper qualified name (too many dotted names): host.regression.public.namelen
\df regres[qrstuv]ion.public.namelen
-database name must be literal: regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
\df nonesuch.public.namelen
cross-database references are not implemented: nonesuch.public.namelen
\dF host.regression.pg_catalog.arabic
improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
\dF regres{1,2}ion.pg_catalog.arabic
-database name must be literal: regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
\dF nonesuch.pg_catalog.arabic
cross-database references are not implemented: nonesuch.pg_catalog.arabic
\dFd host.regression.pg_catalog.arabic_stem
improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
\dFd regres?ion.pg_catalog.arabic_stem
-database name must be literal: regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
\dFd nonesuch.pg_catalog.arabic_stem
cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
\dFp host.regression.pg_catalog.default
improper qualified name (too many dotted names): host.regression.pg_catalog.default
\dFp ^regression.pg_catalog.default
-database name must be literal: ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
\dFp nonesuch.pg_catalog.default
cross-database references are not implemented: nonesuch.pg_catalog.default
\dFt host.regression.pg_catalog.ispell
@@ -5446,7 +5446,7 @@ improper qualified name (too many dotted names): regression.pg_database_owner
\dL host.regression.plpgsql
improper qualified name (too many dotted names): host.regression.plpgsql
\dL *.plpgsql
-database name must be literal: *.plpgsql
+cross-database references are not implemented: *.plpgsql
\dL nonesuch.plpgsql
cross-database references are not implemented: nonesuch.plpgsql
\dn host.regression.public
--
2.24.3 (Apple Git-128)
0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchDownload
From 0c207a07b5524a4d483abc85ce69c7e96f32f941 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 22 Mar 2022 12:24:35 -0400
Subject: [PATCH 1/2] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 38 +-
src/bin/pg_dump/pg_dump.c | 77 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 59 ++++
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 158 ++++++---
src/include/fe_utils/string_utils.h | 8 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 1003 insertions(+), 231 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index caabb06c53..2139673fbd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3633,14 +3633,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..80fffb1d2e 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt, NULL);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt, NULL);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..75d1ebc6fb 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e5816c4cce..998601ad0c 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,26 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1387,17 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1441,17 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+ bool dbname_is_literal;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1494,10 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1513,21 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt, &dbname_is_literal);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ {
+ if (!dbname_is_literal)
+ fatal("database name must be literal: %s", cell->val);
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ }
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1548,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..6e16371314 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1226,10 +1226,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt, NULL);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index fd1052e5db..48b42bda1f 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3848,6 +3848,65 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'ma??.pg_catalog.pg_class' ],
+ qr/pg_dump: error: database name must be literal: ma\?\?\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects non-literal database name'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 714097cad1..ee585084b1 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3557,8 +3599,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3681,10 +3725,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3877,9 +3924,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4092,9 +4141,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4167,8 +4218,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4250,9 +4303,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4324,9 +4379,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4401,8 +4458,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4493,10 +4552,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4595,17 +4656,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4701,9 +4766,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4761,10 +4828,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4875,9 +4944,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4916,9 +4987,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5123,9 +5196,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5184,9 +5259,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5234,9 +5311,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5276,9 +5355,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5448,8 +5529,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5520,8 +5603,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5571,8 +5656,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5638,9 +5725,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5684,10 +5773,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5723,10 +5814,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5808,6 +5901,61 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool dbname_is_literal;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt,
+ &dbname_is_literal);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (!dbname_is_literal)
+ {
+ pg_log_error("database name must be literal: %s", pattern);
+ return false;
+ }
+ else if (PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5859,9 +6007,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5968,9 +6118,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6167,9 +6319,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6270,15 +6424,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6342,8 +6500,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6355,10 +6515,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6436,13 +6598,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6520,12 +6686,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..5e7da77147 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,8 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +892,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt, bool *dbname_is_literal)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +918,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), db, schema, name, pattern,
+ force_escape, true, dotcnt, dbname_is_literal);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +948,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +974,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +982,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1007,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1028,32 +1045,40 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left,
+ left_is_literal;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
+ left_is_literal = true;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1091,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1103,48 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
+ left_is_literal = false;
+ }
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
+ left_is_literal = false;
+ }
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1156,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1182,44 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
+ if (!inquotes && strchr("|+()[]{}.^\\", *cp))
+ left_is_literal = false;
+ }
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
+ if (dbname_is_literal)
+ *dbname_is_literal = left_is_literal;
}
+ else if (dbname_is_literal)
+ *dbname_is_literal = true; /* treat empty dbname as literal */
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..30f4c9caf7 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,14 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer db, int *dotcnt,
+ bool *dbname_is_literal);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt,
+ bool *dbname_is_literal);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..bcf32f7d83 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+database name must be literal: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+database name must be literal: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+database name must be literal: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+database name must be literal: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+database name must be literal: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+database name must be literal: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+database name must be literal: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+database name must be literal: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+database name must be literal: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+database name must be literal: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+database name must be literal: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+database name must be literal: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+database name must be literal: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+database name must be literal: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+database name must be literal: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+database name must be literal: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+database name must be literal: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 0f5287f77b..5edbb0f0da 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,115 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.24.3 (Apple Git-128)
On Mar 22, 2022, at 11:04 AM, Robert Haas <robertmhaas@gmail.com> wrote:
This patch adds three new arguments to processSQLNamePattern() and
documents one of them. It adds three new parameters to
patternToSQLRegex() as well, and documents none of them.
This next patch adds the missing comments.
I think that
the text of the comment might need some updating too, in particular
the sentence "Additional dots in the name portion are not treated as
special."
Changed.
There are no comments explaining the left_is_literal stuff. It appears
that your intention here is that if the pattern string supplied by the
user contains any of *?|+()[]{}.^\ not surrounded by double-quotes, we
signal the caller. Some callers then use this to issue a complaint
that the database name must be a literal. To me, this behavior doesn't
really make sense. If something is a literal, that means we're not
going to interpret the special characters that it contains. Here, we
are interpreting the special characters just so we can complain that
they exist. It seems to me that a simpler solution would be to not
interpret them at all. I attach a patch showing what I mean by that.
It just rips out the dbname_is_literal stuff in favor of doing nothing
at all. To put the whole thing another way, if the user types "\d
}.public.ft", your code wants to complain about the fact that the user
is trying to use regular expression characters in a place where they
are not allowed to do that. I argue that we should instead just be
comparing "}" against the database name and see whether it happens to
match.
I think your change is fine, so I've rolled it into this next patch.
Attachments:
v7-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v7-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From e0ec1ac75f84c97ac5dddbd92f6ca016682d4df8 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 21 Mar 2022 18:30:37 -0700
Subject: [PATCH v7] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 38 +-
src/bin/pg_dump/pg_dump.c | 65 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 53 +++
src/bin/psql/describe.c | 495 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 167 ++++++---
src/include/fe_utils/string_utils.h | 6 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 980 insertions(+), 234 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index caabb06c53..2139673fbd 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3633,14 +3633,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..522dccf15a 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..75d1ebc6fb 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,39 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +198,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +217,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 00629f0836..2470be768f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,21 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1382,16 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1435,16 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1487,9 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1505,17 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1536,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..c35fb05ee5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1226,10 +1226,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 0e724b0366..77cdc30310 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3879,6 +3879,59 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index b8a532a45f..0c251dddc8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return true;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return true;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return true;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return true;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return true;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3625,8 +3667,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3749,10 +3793,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return true;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3945,9 +3992,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4160,9 +4209,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4235,8 +4286,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return true;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4318,9 +4371,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4392,9 +4447,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4469,8 +4526,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4561,10 +4620,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4663,17 +4724,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4769,9 +4834,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4829,10 +4896,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4944,9 +5013,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4985,9 +5056,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5192,9 +5265,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5253,9 +5328,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5303,9 +5380,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5345,9 +5424,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5517,8 +5598,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5589,8 +5672,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5640,8 +5725,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5707,9 +5794,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5753,10 +5842,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5792,10 +5883,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5877,6 +5970,52 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1 &&
+ ((PQdb(pset.db) == NULL || strcmp(PQdb(pset.db), dbbuf.data) != 0)))
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5950,9 +6089,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6066,9 +6207,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6312,9 +6455,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6415,15 +6560,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6487,8 +6636,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return true;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6500,10 +6651,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6581,13 +6734,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6665,12 +6822,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return true;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return true;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..d734d38b37 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,10 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
+ * Can be NULL.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +894,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
+ PQExpBuffer schema = NULL;
+ PQExpBuffer name = NULL;
bool added_clause = false;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -911,16 +920,24 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
return added_clause;
}
- initPQExpBuffer(&schemabuf);
- initPQExpBuffer(&namebuf);
+ if (schemavar)
+ {
+ schema = &schemabuf;
+ initPQExpBuffer(schema);
+ }
+ if (namevar || altnamevar)
+ {
+ name = &namebuf;
+ initPQExpBuffer(name);
+ }
/*
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), dbnamebuf, schema, name, pattern,
+ force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,25 +950,25 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (name && name->len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
/* Optimize away a "*" pattern */
- if (strcmp(namebuf.data, "^(.*)$") != 0)
+ if (strcmp(name->data, "^(.*)$") != 0)
{
WHEREAND();
if (altnamevar)
{
appendPQExpBuffer(buf,
"(%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBuffer(buf,
"\n OR %s OPERATOR(pg_catalog.~) ",
altnamevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferStr(buf, ")\n");
@@ -959,7 +976,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
else
{
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", namevar);
- appendStringLiteralConn(buf, namebuf.data, conn);
+ appendStringLiteralConn(buf, name->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -967,16 +984,16 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schema && schema->len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
/* Optimize away a "*" pattern */
- if (strcmp(schemabuf.data, "^(.*)$") != 0 && schemavar)
+ if (strcmp(schema->data, "^(.*)$") != 0 && schemavar)
{
WHEREAND();
appendPQExpBuffer(buf, "%s OPERATOR(pg_catalog.~) ", schemavar);
- appendStringLiteralConn(buf, schemabuf.data, conn);
+ appendStringLiteralConn(buf, schema->data, conn);
if (PQserverVersion(conn) >= 120000)
appendPQExpBufferStr(buf, " COLLATE pg_catalog.default");
appendPQExpBufferChar(buf, '\n');
@@ -992,8 +1009,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- termPQExpBuffer(&schemabuf);
- termPQExpBuffer(&namebuf);
+ if (schema)
+ termPQExpBuffer(schema);
+ if (name)
+ termPQExpBuffer(name);
return added_clause;
#undef WHEREAND
@@ -1008,8 +1027,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
* contains two or more dbname/schema/name separators, we parse the portions of
* the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * schemabuf, and the rest into namebuf.
*
* If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
* least one separator, we parse the first portion into schemabuf and the rest
@@ -1017,43 +1035,70 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*
* Otherwise, we parse all the pattern into namebuf.
*
+ * If the pattern contains more dotted parts than buffers to parse into, the
+ * extra dots will be treated as literal characters and written into the
+ * namebuf, though they will be counted. Callers should always check the value
+ * returned by reference in dotcnt and handle this error case appropriately.
+ *
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
* else the "^" and "$" will be bound into the first and last alternatives
- * which is not what we want.
+ * which is not what we want. Whether this is done for dbnamebuf is controlled
+ * by the want_literal_dbname parameter.
*
* The regexps we parse into the buffers are appended to the data (if any)
* already present. If we parse fewer fields than the number of buffers we
* were given, the extra buffers are unaltered.
+ *
+ * encoding: the character encoding for the given pattern
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * schemabuf: output parameter receiving the schema name portion of the
+ * pattern, if any. Can be NULL.
+ * namebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * want_literal_dbname: if true, regexp special characters within the database
+ * name portion of the pattern will not be escaped, nor will the dbname be
+ * converted into a regular expression.
+ * dotcnt: output parameter receiving the number of separators parsed from the
+ * pattern.
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left;
const char *cp;
Assert(pattern != NULL);
- Assert(namebuf != NULL);
-
- /* callers should never expect "dbname.relname" format */
- Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
+ maxbuf = &buf[0];
if (dbnamebuf != NULL)
- maxbuf = &buf[2];
- else if (schemabuf != NULL)
- maxbuf = &buf[1];
- else
- maxbuf = &buf[0];
+ maxbuf++;
+ if (schemabuf != NULL)
+ maxbuf++;
+ if (namebuf != NULL)
+ maxbuf++;
curbuf = &buf[0];
+ left = true;
+ if (want_literal_dbname)
+ initPQExpBuffer(&left_literal);
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1111,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1123,40 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf-1)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1168,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1194,35 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left && want_literal_dbname)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
}
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..a228fe4520 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,12 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6428ebc507..64d8cba5a2 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,224 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 0f5287f77b..5edbb0f0da 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,115 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO all
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.35.1
On Fri, Mar 25, 2022 at 3:42 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
I think your change is fine, so I've rolled it into this next patch.
OK, cool. Here are some more comments.
In describe.c, why are the various describeWhatever functions
returning true when validateSQLNamePattern returns false? It seems to
me that they should return false. That would cause exec_command_d() to
set status = PSQL_CMD_ERROR, which seems appropriate. I wondered
whether we should return PSQL_CMD_ERROR only for database errors, but
that doesn't seem to be the case. For example, exec_command_a() sets
PSQL_CMD_ERROR for a failure in do_pset().
pg_dump's prohibit_crossdb_refs() has a special case for you are not
connected to a database, but psql's validateSQLNamePattern() treats it
as an invalid cross-database reference. Maybe that should be
consistent, or just the other way around. After all, I would expect
pg_dump to just bail out if we lose the database connection, but psql
may continue, because we can reconnect. Putting more code into the
tool where reconnecting doesn't really make sense seems odd.
processSQLNamePattern() documents that dotcnt can be NULL, and then
asserts that it isn't.
processSQLNamePattern() introduces new local variables schema and
name, which account for most of the notational churn in that function.
I can't see a reason why those changes are needed. You do test whether
the new variables are NULL in a couple of places, but you could
equally well test schemavar/namevar/altnamevar directly. Actually, I
don't really understand why this function needs any changes other than
passing dbnamebuf and dotcnt through to patternToSQLRegex(). Is there
a reason?
patternToSQLRegex() restructures the system of buffers as well, and I
don't understand the purpose of that either. It sort of looks like the
idea might be to relax the rule against dbname.relname patterns, but
why would we want to do that? If we don't want to do that, why remove
the assertion?
It is not very nice that patternToSQLRegex() ends up repeating the
locution "if (left && want_literal_dbname)
appendPQExpBufferChar(&left_literal, '"')" a whole bunch of times.
Suppose we remove all that. Then, in the if (!inquotes && ch == '.')
case, if left = true, we copy "cp - pattern" bytes starting at
"pattern" into the buffer. Wouldn't that accomplish the same thing
with less code?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mar 29, 2022, at 8:20 AM, Robert Haas <robertmhaas@gmail.com> wrote:
In describe.c, why are the various describeWhatever functions
returning true when validateSQLNamePattern returns false? It seems to
me that they should return false. That would cause exec_command_d() to
set status = PSQL_CMD_ERROR, which seems appropriate. I wondered
whether we should return PSQL_CMD_ERROR only for database errors, but
that doesn't seem to be the case. For example, exec_command_a() sets
PSQL_CMD_ERROR for a failure in do_pset().
Yes, I believe you are right. For scripting, the following should echo, but doesn't under the version 7 patch. Fixed in version 8.
% psql -c "\d a.b.c.d" || echo 'error'
improper qualified name (too many dotted names): a.b.c.d
pg_dump's prohibit_crossdb_refs() has a special case for you are not
connected to a database, but psql's validateSQLNamePattern() treats it
as an invalid cross-database reference. Maybe that should be
consistent, or just the other way around. After all, I would expect
pg_dump to just bail out if we lose the database connection, but psql
may continue, because we can reconnect. Putting more code into the
tool where reconnecting doesn't really make sense seems odd.
Fixed psql in version 8 to issue the appropriate error message, either "You are currently not connected to a database." or "cross-database references are not implemented: %s". That matches the output for pg_dump.
processSQLNamePattern() documents that dotcnt can be NULL, and then
asserts that it isn't.
That's ugly. Fixed the documentation in version 8.
processSQLNamePattern() introduces new local variables schema and
name, which account for most of the notational churn in that function.
I can't see a reason why those changes are needed. You do test whether
the new variables are NULL in a couple of places, but you could
equally well test schemavar/namevar/altnamevar directly. Actually, I
don't really understand why this function needs any changes other than
passing dbnamebuf and dotcnt through to patternToSQLRegex(). Is there
a reason?
It looks like overeager optimization to me, to avoid passing buffers to patternToSQLRegex that aren't really wanted, consequently asking that function to parse things that the caller doesn't care about. But I don't think the optimization is worth the git history churn. Removed in version 8.
patternToSQLRegex() restructures the system of buffers as well, and I
don't understand the purpose of that either. It sort of looks like the
idea might be to relax the rule against dbname.relname patterns, but
why would we want to do that? If we don't want to do that, why remove
the assertion?
This took a while to answer.
I don't remember exactly what I was trying to do here, but it looks like I wanted callers who only want a (possibly database-qualified) schema name to pass that in the (dbnamebuf and) schemabuf, rather than using the (schemabuf and ) namebuf. I obviously didn't finish that conversion, because the clients never got the message. What remained was some rearrangement in patternToSQLRegex which worked but served no purpose.
I've reverted the useless refactoring.
It is not very nice that patternToSQLRegex() ends up repeating the
locution "if (left && want_literal_dbname)
appendPQExpBufferChar(&left_literal, '"')" a whole bunch of times.
Suppose we remove all that. Then, in the if (!inquotes && ch == '.')
case, if left = true, we copy "cp - pattern" bytes starting at
"pattern" into the buffer. Wouldn't that accomplish the same thing
with less code?
We don't *quite* want the literal left string. If it is quoted, we still want the quotes removed. For example:
\d "robert.haas".accounts.acme
needs to return robert.haas (without the quotes) as the database name. Likewise, for embedded quotes:
\d "robert""haas".accounts.acme
needs to return robert"haas, and so forth.
I was able to clean up the "if (left && want_literal_dbname)" stuff, though.
Attachments:
v8-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v8-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From 8737bffb55429b77f5dc34b1f9167096cbf18c21 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 5 Apr 2022 16:08:04 -0700
Subject: [PATCH v8] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 99 +++++-
src/bin/pg_dump/pg_dump.c | 65 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 53 +++
src/bin/psql/describe.c | 502 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 123 +++++--
src/include/fe_utils/string_utils.h | 6 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 1023 insertions(+), 215 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index f01adb1fd2..f82dfc5eb9 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3622,14 +3622,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 6607f72938..522dccf15a 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1334,10 +1334,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1358,12 +1365,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1395,13 +1409,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..6c0f97027d 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,100 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
+# Check that too many dotted names still draws an error under --no-strict-names
+# That flag means that it is ok for the object to be missing, not that it is ok
+# for the object name to be ungrammatical
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-t', 'this.is.a.really.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): this\.is\.a\.really\.long\.dotted\.string/
+ ],
+ 'ungrammatical table names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-s', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical schema names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-d', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical database names still draw errors under --no-strict-names'
+);
+
+# Likewise for exclusion patterns
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-T', 'a.b.c.d' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): a\.b\.c\.d/
+ ],
+ 'ungrammatical table exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-S', 'a.b.c' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b\.c/
+ ],
+ 'ungrammatical schema exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-D', 'a.b' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b/
+ ],
+ 'ungrammatical database exclusions still draw errors under --no-strict-names'
+);
+
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +259,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +278,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 535b160165..c191f36730 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1321,10 +1324,21 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1368,10 +1382,16 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1415,10 +1435,16 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1461,6 +1487,9 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1476,9 +1505,17 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1499,6 +1536,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 9c9f7c6d63..c35fb05ee5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1226,10 +1226,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 75b754a420..e72c163dab 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4005,6 +4005,59 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4dddf08789..6a699912cf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return false;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return false;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return false;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3640,8 +3682,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3764,10 +3808,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return false;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3960,9 +4007,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4175,9 +4224,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4250,8 +4301,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return false;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4333,9 +4386,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4407,9 +4462,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4484,8 +4541,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4576,10 +4635,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4678,17 +4739,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4784,9 +4849,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4844,10 +4911,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4959,9 +5028,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5000,9 +5071,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5207,9 +5280,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5268,9 +5343,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5318,9 +5395,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5360,9 +5439,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5532,8 +5613,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5604,8 +5687,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5655,8 +5740,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5722,9 +5809,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5768,10 +5857,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5807,10 +5898,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5892,6 +5985,59 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (PQdb(pset.db) == NULL)
+ {
+ pg_log_error("You are currently not connected to a database.");
+ return false;
+ }
+ if (strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5965,9 +6111,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6085,9 +6233,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6342,9 +6492,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6445,15 +6597,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6517,8 +6673,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6530,10 +6688,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6611,13 +6771,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6695,12 +6859,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index bca50ec6de..2cd0134e92 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,9 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,7 +893,8 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
@@ -900,6 +904,8 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -919,8 +925,8 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn), dbnamebuf, &schemabuf, &namebuf,
+ pattern, force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -933,7 +939,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (namevar && namebuf.len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
@@ -967,7 +973,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schemavar && schemabuf.len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
@@ -1008,8 +1014,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
* contains two or more dbname/schema/name separators, we parse the portions of
* the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * schemabuf, and the rest into namebuf.
*
* If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
* least one separator, we parse the first portion into schemabuf and the rest
@@ -1017,24 +1022,49 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*
* Otherwise, we parse all the pattern into namebuf.
*
+ * If the pattern contains more dotted parts than buffers to parse into, the
+ * extra dots will be treated as literal characters and written into the
+ * namebuf, though they will be counted. Callers should always check the value
+ * returned by reference in dotcnt and handle this error case appropriately.
+ *
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
* else the "^" and "$" will be bound into the first and last alternatives
- * which is not what we want.
+ * which is not what we want. Whether this is done for dbnamebuf is controlled
+ * by the want_literal_dbname parameter.
*
* The regexps we parse into the buffers are appended to the data (if any)
* already present. If we parse fewer fields than the number of buffers we
* were given, the extra buffers are unaltered.
+ *
+ * encoding: the character encoding for the given pattern
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * schemabuf: output parameter receiving the schema name portion of the
+ * pattern, if any. Can be NULL.
+ * namebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * want_literal_dbname: if true, regexp special characters within the database
+ * name portion of the pattern will not be escaped, nor will the dbname be
+ * converted into a regular expression.
+ * dotcnt: output parameter receiving the number of separators parsed from the
+ * pattern.
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left;
const char *cp;
Assert(pattern != NULL);
@@ -1042,7 +1072,9 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
/* callers should never expect "dbname.relname" format */
Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
@@ -1054,6 +1086,13 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
maxbuf = &buf[0];
curbuf = &buf[0];
+ if (want_literal_dbname)
+ {
+ left = true;
+ initPQExpBuffer(&left_literal);
+ }
+ else
+ left = false;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1066,6 +1105,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1076,32 +1117,40 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1113,6 +1162,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1137,25 +1188,35 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
}
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index 3c88250e6c..a228fe4520 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,12 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 185c505312..95e21d4ff7 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5532,3 +5532,224 @@ SELECT * FROM bla ORDER BY 1;
# final ON_ERROR_ROLLBACK: off
DROP TABLE bla;
DROP FUNCTION psql_error;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 8f49a5f347..27a3c3a26a 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1457,3 +1457,115 @@ SELECT * FROM bla ORDER BY 1;
\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
DROP TABLE bla;
DROP FUNCTION psql_error;
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.35.1
On Wed, Apr 6, 2022 at 12:07 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
I was able to clean up the "if (left && want_literal_dbname)" stuff, though.
I still have a vague feeling that there's probably some way of doing
this better, but had more or less resolved to commit this patch as is
anyway and had that all queued up. But then I had to go to a meeting
and when I came out I discovered that Tom had done this:
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -918,8 +918,12 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer
buf, const char *pattern,
* Convert shell-style 'pattern' into the regular expression(s) we want to
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
+ *
+ * If the caller provided a schemavar, we want to split the pattern on
+ * ".", otherwise not.
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
+ patternToSQLRegex(PQclientEncoding(conn), NULL,
+ (schemavar ? &schemabuf : NULL), &namebuf,
pattern, force_escape);
/*
I don't know whether that's a bug fix for the existing code or some
new bit of functionality that \dconfig requires and nothing else
needs. A related point that I had noticed during review is that these
existing tests look pretty bogus:
if (namebuf.len > 2)
if (schemabuf.len > 2)
In the v13 code, these tests occur at a point where we've definitely
added ^( to the buffer, but possibly nothing else. But starting in v14
that's no longer the case. So probably this test should be changed
somehow. The proposed patch changes these to something like this:
+ if (schemavar && schemabuf.len > 2)
But that doesn't really seem like it's fixing the problem I'm talking about.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
I still have a vague feeling that there's probably some way of doing
this better, but had more or less resolved to commit this patch as is
anyway and had that all queued up. But then I had to go to a meeting
and when I came out I discovered that Tom had done this:
Sorry, it didn't occur to me that that would impinge on what you
were doing over here ... though in retrospect I should have thought
of it.
I don't know whether that's a bug fix for the existing code or some
new bit of functionality that \dconfig requires and nothing else
needs.
Well, \dconfig needs it because it would like foo.bar to get processed
as just a name. But I think it's a bug fix because as things stood,
if the caller doesn't provide a schemavar and the pattern contains a
dot, the code just silently throws away the dot and all to the left.
That doesn't seem very sane, even if it is a longstanding behavior.
regards, tom lane
On Apr 7, 2022, at 3:37 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I don't know whether that's a bug fix for the existing code or some
new bit of functionality that \dconfig requires and nothing else
needs.Well, \dconfig needs it because it would like foo.bar to get processed
as just a name. But I think it's a bug fix because as things stood,
if the caller doesn't provide a schemavar and the pattern contains a
dot, the code just silently throws away the dot and all to the left.
That doesn't seem very sane, even if it is a longstanding behavior.
The patch submitted changes processSQLNamePattern() to return a dot count by reference. It's up to the caller to decide whether to raise an error. If you pass in no schemavar, and you get back dotcnt=2, you know it parsed it as a two part pattern, and you can pg_fatal(...) or ereport(ERROR, ...) or whatever.
It looks like I'll need to post a new version of the patch with an argument telling the function to ignore dots, but I'm not prepared to say that for sure.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Mark Dilger <mark.dilger@enterprisedb.com> writes:
The patch submitted changes processSQLNamePattern() to return a dot count by reference. It's up to the caller to decide whether to raise an error. If you pass in no schemavar, and you get back dotcnt=2, you know it parsed it as a two part pattern, and you can pg_fatal(...) or ereport(ERROR, ...) or whatever.
Well, I'm not telling Robert what to do, but I wouldn't accept that
API. It requires duplicative error-handling code at every call site
and is an open invitation to omitting necessary error checks.
Possibly a better idea is to add an enum argument telling the function
what to do (parse the whole thing as one name regardless of dots,
parse as two names if there's a dot, throw error if there's a dot,
etc etc as needed by existing call sites). Perhaps some of the
existing arguments could be merged into such an enum, too.
regards, tom lane
On Thu, Apr 7, 2022 at 7:41 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
The patch submitted changes processSQLNamePattern() to return a dot count by reference. It's up to the caller to decide whether to raise an error. If you pass in no schemavar, and you get back dotcnt=2, you know it parsed it as a two part pattern, and you can pg_fatal(...) or ereport(ERROR, ...) or whatever.
Well, I'm not telling Robert what to do, but I wouldn't accept that
API. It requires duplicative error-handling code at every call site
and is an open invitation to omitting necessary error checks.Possibly a better idea is to add an enum argument telling the function
what to do (parse the whole thing as one name regardless of dots,
parse as two names if there's a dot, throw error if there's a dot,
etc etc as needed by existing call sites). Perhaps some of the
existing arguments could be merged into such an enum, too.
I hadn't considered that approach, but I don't think it works very
well, because front-end error handling is so inconsistent. From the
patch:
+ pg_log_error("improper relation name (too many dotted
names): %s", pattern);
+ exit(2);
+ fatal("improper qualified name (too many
dotted names): %s",
+ cell->val);
+ pg_log_error("improper qualified name (too
many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ pg_log_error("improper qualified name (too many dotted
names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
Come to think of it, maybe the error text there could stand some
bikeshedding, but AFAICS there's not much to be done about the fact
that one caller wants pg_log_error + exit(2), another wants fatal(), a
third PQfinish(conn) and exit_nicely(), and the last termPQExpBuffer()
and return false.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Apr 07, 2022 at 10:26:18PM -0400, Robert Haas wrote:
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
Come to think of it, maybe the error text there could stand some
bikeshedding, but AFAICS
AFAICT the error text deliberately matches this, which I mentioned seems to me
the strongest argument for supporting \d datname.nspname.relname
ts=# SELECT 'a.a.a.a'::regclass;
ERROR: 42601: improper relation name (too many dotted names): a.a.a.a
LINE 1: SELECT 'a.a.a.a'::regclass;
^
LOCATION: makeRangeVarFromNameList, namespace.c:3129
On Thu, 7 Apr 2022 at 22:32, Robert Haas <robertmhaas@gmail.com> wrote:
On Thu, Apr 7, 2022 at 7:41 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Possibly a better idea is to add an enum argument telling the function
what to do (parse the whole thing as one name regardless of dots,
parse as two names if there's a dot, throw error if there's a dot,
etc etc as needed by existing call sites). Perhaps some of the
existing arguments could be merged into such an enum, too.AFAICS there's not much to be done about the fact
that one caller wants pg_log_error + exit(2), another wants fatal(), a
third PQfinish(conn) and exit_nicely(), and the last termPQExpBuffer()
and return false.
That doesn't seem to be entirely inconsistent with what Tom describes.
Instead of "throw an error" the function would return an error and
possibly some extra info which the caller would use to handle the
error appropriately.
It still has the nice property that the decision that it is in fact an
error would be made inside the parsing function based on the enum
declaring what's intended. And it wouldn't return a possibly bogus
parsing with information the caller might use to infer it isn't what
was desired (or fail to).
--
greg
On Thu, Apr 7, 2022 at 11:40 PM Greg Stark <stark@mit.edu> wrote:
That doesn't seem to be entirely inconsistent with what Tom describes.
Instead of "throw an error" the function would return an error and
possibly some extra info which the caller would use to handle the
error appropriately.
I don't personally see how we're going to come out ahead with that
approach, but if you or Tom or someone else want to put something
together, that's fine with me. I'm not stuck on this approach, I just
don't see how we come out ahead with the type of thing you're talking
about. I mean we could return the error text, but it's only to a
handful of places, so it just doesn't really seem like a win over what
the patch is already doing.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Apr 8, 2022, at 4:11 AM, Robert Haas <robertmhaas@gmail.com> wrote:
I don't personally see how we're going to come out ahead with that
approach, but if you or Tom or someone else want to put something
together, that's fine with me. I'm not stuck on this approach, I just
don't see how we come out ahead with the type of thing you're talking
about. I mean we could return the error text, but it's only to a
handful of places, so it just doesn't really seem like a win over what
the patch is already doing.
Since there hasn't been any agreement on that point, I've just rebased the patch to apply cleanly against the current master:
Attachments:
v9-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v9-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From 8c9e8de44d3b33cab1eba5e546d7936f6a60704a Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 18 Apr 2022 10:54:18 -0700
Subject: [PATCH v9] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 99 +++++-
src/bin/pg_dump/pg_dump.c | 65 +++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 89 +++++
src/bin/psql/describe.c | 504 ++++++++++++++++++---------
src/fe_utils/string_utils.c | 123 +++++--
src/include/fe_utils/string_utils.h | 6 +-
src/test/regress/expected/psql.out | 221 ++++++++++++
src/test/regress/sql/psql.sql | 112 ++++++
11 files changed, 1061 insertions(+), 215 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 592356019b..c6b6ec366c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3641,14 +3641,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 90471e096d..48cee8c1c4 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1308,10 +1308,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1332,12 +1339,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1369,13 +1383,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..6c0f97027d 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,100 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
+# Check that too many dotted names still draws an error under --no-strict-names
+# That flag means that it is ok for the object to be missing, not that it is ok
+# for the object name to be ungrammatical
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-t', 'this.is.a.really.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): this\.is\.a\.really\.long\.dotted\.string/
+ ],
+ 'ungrammatical table names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-s', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical schema names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-d', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical database names still draw errors under --no-strict-names'
+);
+
+# Likewise for exclusion patterns
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-T', 'a.b.c.d' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): a\.b\.c\.d/
+ ],
+ 'ungrammatical table exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-S', 'a.b.c' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b\.c/
+ ],
+ 'ungrammatical schema exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-D', 'a.b' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b/
+ ],
+ 'ungrammatical database exclusions still draw errors under --no-strict-names'
+);
+
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +259,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +278,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 969e2a7a46..d3588607e7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1315,10 +1318,21 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt);
+ if (dotcnt > 1)
+ pg_fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1362,10 +1376,16 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ pg_fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1409,10 +1429,16 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ pg_fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1455,6 +1481,9 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1470,9 +1499,17 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt);
+ if (dotcnt > 2)
+ pg_fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1493,6 +1530,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ pg_fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ pg_fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 79a723885e..52f9f7c4d6 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1269,10 +1269,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c65c92bfb0..066ffe7dec 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3973,6 +3973,95 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', '.' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \./,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern "."'
+);
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', '.*' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \.\*/,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern ".*"'
+);
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', '*.*' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \*\.\*/,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern "*.*"'
+);
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '.' ],
+ qr/pg_dump: error: cross-database references are not implemented: \./,
+ 'pg_dump: option --schema rejects degenerate two-part schema name: "."'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '.*' ],
+ qr/pg_dump: error: cross-database references are not implemented: \.\*/,
+ 'pg_dump: option --schema rejects degenerate two-part schema name: ".*"'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '..' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): \.\./,
+ 'pg_dump: option --schema rejects degenerate three-part schema name: ".."'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '.*.*' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): \.\*\.\*/,
+ 'pg_dump: option --schema rejects degenerate three-part schema pattern: ".*.*"'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 583817b0cc..4369f2235b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return false;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return false;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return false;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3572,8 +3614,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3696,10 +3740,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return false;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3892,9 +3939,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4107,9 +4156,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4182,8 +4233,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return false;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4265,9 +4318,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4339,9 +4394,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4406,7 +4463,7 @@ describeConfigurationParameters(const char *pattern, bool verbose,
processSQLNamePattern(pset.db, &buf, pattern,
false, false,
NULL, "pg_catalog.lower(s.name)", NULL,
- NULL);
+ NULL, NULL, NULL);
else
appendPQExpBufferStr(&buf, "WHERE s.source <> 'default' AND\n"
" s.setting IS DISTINCT FROM s.boot_val\n");
@@ -4485,8 +4542,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4577,10 +4636,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4679,17 +4740,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4785,9 +4850,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4845,10 +4912,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4959,9 +5028,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5000,9 +5071,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5207,9 +5280,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5268,9 +5343,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5318,9 +5395,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5360,9 +5439,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5532,8 +5613,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5604,8 +5687,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5655,8 +5740,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5722,9 +5809,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5768,10 +5857,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5807,10 +5898,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5892,6 +5985,59 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (PQdb(pset.db) == NULL)
+ {
+ pg_log_error("You are currently not connected to a database.");
+ return false;
+ }
+ if (strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5943,9 +6089,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6056,9 +6204,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6266,9 +6416,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6369,15 +6521,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6441,8 +6597,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6454,10 +6612,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6535,13 +6695,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6619,12 +6783,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 1c61840462..60c229a7f5 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,9 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +893,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
bool added_clause = false;
+ int dcnt;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ if (dotcnt == NULL)
+ dotcnt = &dcnt;
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -924,7 +932,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*/
patternToSQLRegex(PQclientEncoding(conn), NULL,
(schemavar ? &schemabuf : NULL), &namebuf,
- pattern, force_escape);
+ pattern, force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -937,7 +945,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (namevar && namebuf.len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
@@ -971,7 +979,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schemavar && schemabuf.len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
@@ -1012,8 +1020,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
* contains two or more dbname/schema/name separators, we parse the portions of
* the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * schemabuf, and the rest into namebuf.
*
* If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
* least one separator, we parse the first portion into schemabuf and the rest
@@ -1021,24 +1028,49 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*
* Otherwise, we parse all the pattern into namebuf.
*
+ * If the pattern contains more dotted parts than buffers to parse into, the
+ * extra dots will be treated as literal characters and written into the
+ * namebuf, though they will be counted. Callers should always check the value
+ * returned by reference in dotcnt and handle this error case appropriately.
+ *
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
* else the "^" and "$" will be bound into the first and last alternatives
- * which is not what we want.
+ * which is not what we want. Whether this is done for dbnamebuf is controlled
+ * by the want_literal_dbname parameter.
*
* The regexps we parse into the buffers are appended to the data (if any)
* already present. If we parse fewer fields than the number of buffers we
* were given, the extra buffers are unaltered.
+ *
+ * encoding: the character encoding for the given pattern
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * schemabuf: output parameter receiving the schema name portion of the
+ * pattern, if any. Can be NULL.
+ * namebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * want_literal_dbname: if true, regexp special characters within the database
+ * name portion of the pattern will not be escaped, nor will the dbname be
+ * converted into a regular expression.
+ * dotcnt: output parameter receiving the number of separators parsed from the
+ * pattern.
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left;
const char *cp;
Assert(pattern != NULL);
@@ -1046,7 +1078,9 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
/* callers should never expect "dbname.relname" format */
Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
@@ -1058,6 +1092,13 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
maxbuf = &buf[0];
curbuf = &buf[0];
+ if (want_literal_dbname)
+ {
+ left = true;
+ initPQExpBuffer(&left_literal);
+ }
+ else
+ left = false;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1070,6 +1111,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1080,32 +1123,40 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1117,6 +1168,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1141,25 +1194,35 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
}
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index b9b8708dab..fa4deb2497 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,12 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 8e11ebbcaa..6937057e91 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5549,3 +5549,224 @@ SELECT * FROM bla ORDER BY 1;
# final ON_ERROR_ROLLBACK: off
DROP TABLE bla;
DROP FUNCTION psql_error;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index bf372c37a5..0a19aaf630 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1463,3 +1463,115 @@ SELECT * FROM bla ORDER BY 1;
\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
DROP TABLE bla;
DROP FUNCTION psql_error;
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
--
2.35.1
On Mon, Apr 18, 2022 at 3:39 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
Since there hasn't been any agreement on that point, I've just rebased the patch to apply cleanly against the current master:
This looks OK to me. There may be better ways to do some of it, but
there's no rule against further improving the code later. Also, since
the issue was introduced in v14, we probably shouldn't wait forever to
do something about it. However, there is a procedural issue here now
that we are past feature freeze. I think someone could defensibly take
any of the following positions:
(A) This is a new feature. Wait for v16.
(B) This is a bug fix. Commit it now and back-patch to v14.
(C) This is a cleanup that is OK to put into v15 even after feature
freeze but since it is a behavior change we shouldn't back-patch it.
I vote for (C). What do other people think?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Apr 19, 2022 at 7:00 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Apr 18, 2022 at 3:39 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:Since there hasn't been any agreement on that point, I've just rebased
the patch to apply cleanly against the current master:
This looks OK to me. There may be better ways to do some of it, but
there's no rule against further improving the code later. Also, since
the issue was introduced in v14, we probably shouldn't wait forever to
do something about it. However, there is a procedural issue here now
that we are past feature freeze. I think someone could defensibly take
any of the following positions:(A) This is a new feature. Wait for v16.
(B) This is a bug fix. Commit it now and back-patch to v14.
(C) This is a cleanup that is OK to put into v15 even after feature
freeze but since it is a behavior change we shouldn't back-patch it.I vote for (C). What do other people think?
I vote for (B). The behavioral change for v14 turns working usage patterns
into errors where it should not have. It is a design bug and POLA
violation that should be corrected.
"""
such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
"""
My concern here about a behavior affecting bug fix - which we allow - is
reduced by the fact this feature is almost exclusively an interactive one.
Which supports not having only v14, and maybe v15, behave differently than
v13 and v16 when it comes to using it for expected usage patterns:
"""
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
"""
David J.
On Tue, Apr 19, 2022 at 10:00:01AM -0400, Robert Haas wrote:
(A) This is a new feature. Wait for v16.
(B) This is a bug fix. Commit it now and back-patch to v14.
(C) This is a cleanup that is OK to put into v15 even after feature
freeze but since it is a behavior change we shouldn't back-patch it.I vote for (C). What do other people think?
I thought the plan was to backpatch to v14.
v14 psql had an unintentional behavior change, rejecting \d
datname.nspname.relname.
This patch is meant to relax that change by allowing datname, but only if it
matches the name of the current database ... without returning to the v13
behavior, which allowed arbitrary leading junk.
--
Justin
Justin Pryzby <pryzby@telsasoft.com> writes:
On Tue, Apr 19, 2022 at 10:00:01AM -0400, Robert Haas wrote:
(A) This is a new feature. Wait for v16.
(B) This is a bug fix. Commit it now and back-patch to v14.
(C) This is a cleanup that is OK to put into v15 even after feature
freeze but since it is a behavior change we shouldn't back-patch it.
I vote for (C). What do other people think?
I thought the plan was to backpatch to v14.
v14 psql had an unintentional behavior change, rejecting \d
datname.nspname.relname.
I agree that the v14 behavior is a bug, so ordinarily I'd vote
for back-patching.
A possible objection to doing that is that the patch changes the
APIs of processSQLNamePattern and patternToSQLRegex. We would avoid
making such a change in core-backend APIs in a minor release, but
I'm not certain whether there are equivalent stability concerns
for src/fe_utils/.
On the whole I'd vote for (B), with (C) as second choice.
regards, tom lane
On 4/19/22 16:00, Robert Haas wrote:
On Mon, Apr 18, 2022 at 3:39 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:Since there hasn't been any agreement on that point, I've just rebased the patch to apply cleanly against the current master:
This looks OK to me. There may be better ways to do some of it, but
there's no rule against further improving the code later. Also, since
the issue was introduced in v14, we probably shouldn't wait forever to
do something about it. However, there is a procedural issue here now
that we are past feature freeze. I think someone could defensibly take
any of the following positions:(A) This is a new feature. Wait for v16.
(B) This is a bug fix. Commit it now and back-patch to v14.
(C) This is a cleanup that is OK to put into v15 even after feature
freeze but since it is a behavior change we shouldn't back-patch it.I vote for (C). What do other people think?
I vote for (B).
--
Vik Fearing
On Apr 19, 2022, at 7:00 AM, Robert Haas <robertmhaas@gmail.com> wrote:
(A) This is a new feature. Wait for v16.
(B) This is a bug fix. Commit it now and back-patch to v14.
(C) This is a cleanup that is OK to put into v15 even after feature
freeze but since it is a behavior change we shouldn't back-patch it.I vote for (C). What do other people think?
Looks like most people voted for (B). In support of that option, here are patches for master and REL_14_STABLE. Note that I extended the tests compared to v9, which found a problem that is fixed for v10:
Attachments:
v14-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v14-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From f44a4bbec27e51fd28051004df1bdfd9b5fe599e Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 19 Apr 2022 12:17:40 -0700
Subject: [PATCH v14] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 101 +++-
src/bin/pg_dump/pg_dump.c | 64 ++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 109 +++-
src/bin/psql/describe.c | 508 +++++++++++------
src/fe_utils/string_utils.c | 127 +++--
src/include/fe_utils/string_utils.h | 6 +-
src/test/regress/expected/psql.out | 804 +++++++++++++++++++++++++++
src/test/regress/sql/psql.sql | 242 ++++++++
11 files changed, 1799 insertions(+), 219 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 60069c1562..d14f0581e6 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3595,14 +3595,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 96e619512a..b4ce47a2e9 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1341,10 +1341,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1365,12 +1372,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1402,13 +1416,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index b802e44d61..df0cb036cd 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -6,7 +6,7 @@ use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 76;
+use Test::More tests => 100;
# Test set-up
my ($node, $port);
@@ -147,6 +147,100 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
+# Check that too many dotted names still draws an error under --no-strict-names
+# That flag means that it is ok for the object to be missing, not that it is ok
+# for the object name to be ungrammatical
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-t', 'this.is.a.really.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): this\.is\.a\.really\.long\.dotted\.string/
+ ],
+ 'ungrammatical table names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-s', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical schema names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-d', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical database names still draw errors under --no-strict-names'
+);
+
+# Likewise for exclusion patterns
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-T', 'a.b.c.d' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): a\.b\.c\.d/
+ ],
+ 'ungrammatical table exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-S', 'a.b.c' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b\.c/
+ ],
+ 'ungrammatical schema exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-D', 'a.b' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b/
+ ],
+ 'ungrammatical database exclusions still draw errors under --no-strict-names'
+);
+
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +259,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +278,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 30d23032fe..e110257395 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -164,6 +164,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1358,10 +1361,21 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt);
+ if (dotcnt > 1)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1405,10 +1419,16 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1452,10 +1472,16 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1498,6 +1524,9 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1513,9 +1542,17 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt);
+ if (dotcnt > 2)
+ fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1536,6 +1573,25 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ fatal("cross-database references are not implemented: %s", pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index c29101704a..f8ea8236ff 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1438,10 +1438,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index b52cfe9f73..2c0167fdc5 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3602,7 +3602,7 @@ $node->psql('postgres', 'create database regress_pg_dump_test;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)
-my $num_tests = 12;
+my $num_tests = 42;
foreach my $run (sort keys %pgdump_runs)
{
@@ -3775,6 +3775,113 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+command_fails_like(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', '.' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \./,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern "."'
+);
+
+command_fails_like(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', '.*' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \.\*/,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern ".*"'
+);
+
+command_fails_like(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', '*.*' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \*\.\*/,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern "*.*"'
+);
+
+command_fails_like(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+
+$node->command_ok(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', '"myhost.mydb"' ],
+ 'pg_dumpall: option --exclude-database handles database names with embedded dots'
+);
+
+$node->command_ok(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', '"some.other.db".myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: "some\.other\.db"\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names with embedded dots'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', '.' ],
+ qr/pg_dump: error: cross-database references are not implemented: \./,
+ 'pg_dump: option --schema rejects degenerate two-part schema name: "."'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', '.*' ],
+ qr/pg_dump: error: cross-database references are not implemented: \.\*/,
+ 'pg_dump: option --schema rejects degenerate two-part schema name: ".*"'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', '..' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): \.\./,
+ 'pg_dump: option --schema rejects degenerate three-part schema name: ".."'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--schema', '.*.*' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): \.\*\.\*/,
+ 'pg_dump: option --schema rejects degenerate three-part schema pattern: ".*.*"'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--table', '"some.other.db".pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: "some\.other\.db"\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names with embedded dots'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index db767d78f0..8fe39475fa 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -45,6 +45,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -121,9 +127,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -189,9 +197,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -276,9 +286,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -640,9 +652,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -664,10 +678,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -805,11 +821,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -959,10 +977,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return false;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -987,10 +1007,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -1068,8 +1090,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1219,9 +1243,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1296,11 +1322,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1372,9 +1400,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1394,9 +1424,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
/*
@@ -1422,9 +1454,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return false;
}
/*
@@ -1451,9 +1485,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return false;
}
/* Rule descriptions (ignore rules for views) */
@@ -1473,9 +1509,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1493,9 +1531,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1549,9 +1589,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3807,8 +3849,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return false;
}
else
{
@@ -3822,8 +3866,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
" ARRAY(SELECT g.groname FROM pg_catalog.pg_group g WHERE u.usesysid = ANY(g.grolist)) as memberof"
"\nFROM pg_catalog.pg_user u\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "u.usename", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "u.usename", NULL, NULL,
+ NULL, 1))
+ return false;
}
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3958,10 +4004,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return false;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -4176,9 +4225,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4391,9 +4442,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4471,8 +4524,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return false;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4561,9 +4616,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4635,9 +4692,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4702,8 +4761,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4794,10 +4855,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4903,17 +4966,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -5010,9 +5077,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5068,10 +5137,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5129,9 +5200,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5170,9 +5243,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5387,9 +5462,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5458,9 +5535,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5518,9 +5597,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5560,9 +5641,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5748,8 +5831,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5830,8 +5915,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5891,8 +5978,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5968,9 +6057,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -6024,10 +6115,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6073,10 +6166,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6158,6 +6253,59 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (PQdb(pset.db) == NULL)
+ {
+ pg_log_error("You are currently not connected to a database.");
+ return false;
+ }
+ if (strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -6209,9 +6357,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6276,9 +6426,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6460,9 +6612,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6568,15 +6722,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6640,8 +6798,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6653,10 +6813,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6734,13 +6896,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6818,12 +6984,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 3efee4e7ee..5f34741426 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -819,6 +819,9 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -827,16 +830,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
bool added_clause = false;
+ int dcnt;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ if (dotcnt == NULL)
+ dotcnt = &dcnt;
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -856,8 +864,10 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* execute. Quoting/escaping into SQL literal format will be done below
* using appendStringLiteralConn().
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL, &schemabuf, &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn),
+ (schemavar ? dbnamebuf : NULL),
+ (schemavar ? &schemabuf: NULL),
+ &namebuf, pattern, force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -870,7 +880,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (namevar && namebuf.len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
@@ -904,7 +914,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schemavar && schemabuf.len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
@@ -945,8 +955,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
* contains two or more dbname/schema/name separators, we parse the portions of
* the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * schemabuf, and the rest into namebuf.
*
* If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
* least one separator, we parse the first portion into schemabuf and the rest
@@ -954,24 +963,49 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*
* Otherwise, we parse all the pattern into namebuf.
*
+ * If the pattern contains more dotted parts than buffers to parse into, the
+ * extra dots will be treated as literal characters and written into the
+ * namebuf, though they will be counted. Callers should always check the value
+ * returned by reference in dotcnt and handle this error case appropriately.
+ *
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
* else the "^" and "$" will be bound into the first and last alternatives
- * which is not what we want.
+ * which is not what we want. Whether this is done for dbnamebuf is controlled
+ * by the want_literal_dbname parameter.
*
* The regexps we parse into the buffers are appended to the data (if any)
* already present. If we parse fewer fields than the number of buffers we
* were given, the extra buffers are unaltered.
+ *
+ * encoding: the character encoding for the given pattern
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * schemabuf: output parameter receiving the schema name portion of the
+ * pattern, if any. Can be NULL.
+ * namebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * want_literal_dbname: if true, regexp special characters within the database
+ * name portion of the pattern will not be escaped, nor will the dbname be
+ * converted into a regular expression.
+ * dotcnt: output parameter receiving the number of separators parsed from the
+ * pattern.
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left;
const char *cp;
Assert(pattern != NULL);
@@ -979,7 +1013,9 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
/* callers should never expect "dbname.relname" format */
Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
@@ -991,6 +1027,13 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
maxbuf = &buf[0];
curbuf = &buf[0];
+ if (want_literal_dbname)
+ {
+ left = true;
+ initPQExpBuffer(&left_literal);
+ }
+ else
+ left = false;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1003,6 +1046,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1013,32 +1058,40 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1050,6 +1103,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1074,25 +1129,35 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
}
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index caafb97d29..0cc4b212f7 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -54,10 +54,12 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..bff68bd27f 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5179,3 +5179,807 @@ List of access methods
pg_catalog | && | anyarray | anyarray | boolean | overlaps
(1 row)
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
+-- check that dots within quoted name segments are not counted
+\dA "no.such.access.method"
+List of access methods
+ Name | Type
+------+------
+(0 rows)
+
+\dt "no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da "no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dAc "no.such.operator.class"
+ List of operator classes
+ AM | Input type | Storage type | Operator class | Default?
+----+------------+--------------+----------------+----------
+(0 rows)
+
+\dAf "no.such.operator.family"
+ List of operator families
+ AM | Operator family | Applicable types
+----+-----------------+------------------
+(0 rows)
+
+\dAo "no.such.operator.of.operator.family"
+ List of operators of operator families
+ AM | Operator family | Operator | Strategy | Purpose
+----+-----------------+----------+----------+---------
+(0 rows)
+
+\dAp "no.such.operator.support.function.of.operator.family"
+ List of support functions of operator families
+ AM | Operator family | Registered left type | Registered right type | Number | Function
+----+-----------------+----------------------+-----------------------+--------+----------
+(0 rows)
+
+\db "no.such.tablespace"
+ List of tablespaces
+ Name | Owner | Location
+------+-------+----------
+(0 rows)
+
+\dc "no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC "no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd "no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD "no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\ddp "no.such.default.access.privilege"
+ Default access privileges
+ Owner | Schema | Type | Access privileges
+-------+--------+------+-------------------
+(0 rows)
+
+\di "no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm "no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\des "no.such.foreign.server"
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+-------+----------------------
+(0 rows)
+
+\dew "no.such.foreign.data.wrapper"
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator
+------+-------+---------+-----------
+(0 rows)
+
+\df "no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF "no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd "no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp "no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt "no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dg "no.such.role"
+ List of roles
+ Role name | Attributes | Member of
+-----------+------------+-----------
+
+\dL "no.such.language"
+ List of languages
+ Name | Owner | Trusted | Description
+------+-------+---------+-------------
+(0 rows)
+
+\dn "no.such.schema"
+List of schemas
+ Name | Owner
+------+-------
+(0 rows)
+
+\do "no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO "no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | Provider | Deterministic?
+--------+------+---------+-------+----------+----------------
+(0 rows)
+
+\dp "no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP "no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\drds "no.such.setting"
+ List of settings
+ Role | Database | Settings
+------+----------+----------
+(0 rows)
+
+\dRp "no.such.publication"
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+------+-------+------------+---------+---------+---------+-----------+----------
+(0 rows)
+
+\dRs "no.such.subscription"
+ List of subscriptions
+ Name | Owner | Enabled | Publication
+------+-------+---------+-------------
+(0 rows)
+
+\dT "no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dx "no.such.installed.extension"
+ List of installed extensions
+ Name | Version | Schema | Description
+------+---------+--------+-------------
+(0 rows)
+
+\dX "no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+\dy "no.such.event.trigger"
+ List of event triggers
+ Name | Event | Owner | Enabled | Function | Tags
+------+-------+-------+---------+----------+------
+(0 rows)
+
+-- again, but with dotted schema qualifications.
+\dA "no.such.schema"."no.such.access.method"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.access.method"
+\dt "no.such.schema"."no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da "no.such.schema"."no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dAc "no.such.schema"."no.such.operator.class"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.class"
+\dAf "no.such.schema"."no.such.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.family"
+\dAo "no.such.schema"."no.such.operator.of.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.of.operator.family"
+\dAp "no.such.schema"."no.such.operator.support.function.of.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.support.function.of.operator.family"
+\db "no.such.schema"."no.such.tablespace"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.tablespace"
+\dc "no.such.schema"."no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC "no.such.schema"."no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd "no.such.schema"."no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD "no.such.schema"."no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\ddp "no.such.schema"."no.such.default.access.privilege"
+ Default access privileges
+ Owner | Schema | Type | Access privileges
+-------+--------+------+-------------------
+(0 rows)
+
+\di "no.such.schema"."no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm "no.such.schema"."no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\des "no.such.schema"."no.such.foreign.server"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.foreign.server"
+\dew "no.such.schema"."no.such.foreign.data.wrapper"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.foreign.data.wrapper"
+\df "no.such.schema"."no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF "no.such.schema"."no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd "no.such.schema"."no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp "no.such.schema"."no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt "no.such.schema"."no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dg "no.such.schema"."no.such.role"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.role"
+\dL "no.such.schema"."no.such.language"
+cross-database references are not implemented: "no.such.schema"."no.such.language"
+\do "no.such.schema"."no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO "no.such.schema"."no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | Provider | Deterministic?
+--------+------+---------+-------+----------+----------------
+(0 rows)
+
+\dp "no.such.schema"."no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP "no.such.schema"."no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\drds "no.such.schema"."no.such.setting"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.setting"
+\dRp "no.such.schema"."no.such.publication"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.publication"
+\dRs "no.such.schema"."no.such.subscription"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.subscription"
+\dT "no.such.schema"."no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dx "no.such.schema"."no.such.installed.extension"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.installed.extension"
+\dX "no.such.schema"."no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+\dy "no.such.schema"."no.such.event.trigger"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
+-- again, but with current database and dotted schema qualifications.
+\dt regression."no.such.schema"."no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da regression."no.such.schema"."no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dc regression."no.such.schema"."no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC regression."no.such.schema"."no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd regression."no.such.schema"."no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD regression."no.such.schema"."no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\di regression."no.such.schema"."no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm regression."no.such.schema"."no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\df regression."no.such.schema"."no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF regression."no.such.schema"."no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd regression."no.such.schema"."no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp regression."no.such.schema"."no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt regression."no.such.schema"."no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\do regression."no.such.schema"."no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO regression."no.such.schema"."no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | Provider | Deterministic?
+--------+------+---------+-------+----------+----------------
+(0 rows)
+
+\dp regression."no.such.schema"."no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP regression."no.such.schema"."no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\dT regression."no.such.schema"."no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dX regression."no.such.schema"."no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+-- again, but with dotted database and dotted schema qualifications.
+\dt "no.such.database"."no.such.schema"."no.such.table.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.table.relation"
+\da "no.such.database"."no.such.schema"."no.such.aggregate.function"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.aggregate.function"
+\dc "no.such.database"."no.such.schema"."no.such.conversion"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.conversion"
+\dC "no.such.database"."no.such.schema"."no.such.cast"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.cast"
+\dd "no.such.database"."no.such.schema"."no.such.object.description"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.object.description"
+\dD "no.such.database"."no.such.schema"."no.such.domain"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.domain"
+\ddp "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+\di "no.such.database"."no.such.schema"."no.such.index.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.index.relation"
+\dm "no.such.database"."no.such.schema"."no.such.materialized.view"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.materialized.view"
+\ds "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\dt "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\dv "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\df "no.such.database"."no.such.schema"."no.such.function"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.function"
+\dF "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.database"."no.such.schema"."no.such.text.search.parser"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.database"."no.such.schema"."no.such.text.search.template"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.template"
+\do "no.such.database"."no.such.schema"."no.such.operator"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.operator"
+\dO "no.such.database"."no.such.schema"."no.such.collation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.collation"
+\dp "no.such.database"."no.such.schema"."no.such.access.privilege"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.access.privilege"
+\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+\dT "no.such.database"."no.such.schema"."no.such.data.type"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type"
+\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics"
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 68121d171c..306c1d57a8 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1241,3 +1241,245 @@ drop role regress_partitioning_role;
\dfa bit* small*
\do - pg_catalog.int4
\do && anyarray *
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
+
+-- check that dots within quoted name segments are not counted
+\dA "no.such.access.method"
+\dt "no.such.table.relation"
+\da "no.such.aggregate.function"
+\dAc "no.such.operator.class"
+\dAf "no.such.operator.family"
+\dAo "no.such.operator.of.operator.family"
+\dAp "no.such.operator.support.function.of.operator.family"
+\db "no.such.tablespace"
+\dc "no.such.conversion"
+\dC "no.such.cast"
+\dd "no.such.object.description"
+\dD "no.such.domain"
+\ddp "no.such.default.access.privilege"
+\di "no.such.index.relation"
+\dm "no.such.materialized.view"
+\ds "no.such.relation"
+\dt "no.such.relation"
+\dv "no.such.relation"
+\des "no.such.foreign.server"
+\dew "no.such.foreign.data.wrapper"
+\df "no.such.function"
+\dF "no.such.text.search.configuration"
+\dFd "no.such.text.search.dictionary"
+\dFp "no.such.text.search.parser"
+\dFt "no.such.text.search.template"
+\dg "no.such.role"
+\dL "no.such.language"
+\dn "no.such.schema"
+\do "no.such.operator"
+\dO "no.such.collation"
+\dp "no.such.access.privilege"
+\dP "no.such.partitioned.relation"
+\drds "no.such.setting"
+\dRp "no.such.publication"
+\dRs "no.such.subscription"
+\dT "no.such.data.type"
+\dx "no.such.installed.extension"
+\dX "no.such.extended.statistics"
+\dy "no.such.event.trigger"
+
+-- again, but with dotted schema qualifications.
+\dA "no.such.schema"."no.such.access.method"
+\dt "no.such.schema"."no.such.table.relation"
+\da "no.such.schema"."no.such.aggregate.function"
+\dAc "no.such.schema"."no.such.operator.class"
+\dAf "no.such.schema"."no.such.operator.family"
+\dAo "no.such.schema"."no.such.operator.of.operator.family"
+\dAp "no.such.schema"."no.such.operator.support.function.of.operator.family"
+\db "no.such.schema"."no.such.tablespace"
+\dc "no.such.schema"."no.such.conversion"
+\dC "no.such.schema"."no.such.cast"
+\dd "no.such.schema"."no.such.object.description"
+\dD "no.such.schema"."no.such.domain"
+\ddp "no.such.schema"."no.such.default.access.privilege"
+\di "no.such.schema"."no.such.index.relation"
+\dm "no.such.schema"."no.such.materialized.view"
+\ds "no.such.schema"."no.such.relation"
+\dt "no.such.schema"."no.such.relation"
+\dv "no.such.schema"."no.such.relation"
+\des "no.such.schema"."no.such.foreign.server"
+\dew "no.such.schema"."no.such.foreign.data.wrapper"
+\df "no.such.schema"."no.such.function"
+\dF "no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.schema"."no.such.text.search.template"
+\dg "no.such.schema"."no.such.role"
+\dL "no.such.schema"."no.such.language"
+\do "no.such.schema"."no.such.operator"
+\dO "no.such.schema"."no.such.collation"
+\dp "no.such.schema"."no.such.access.privilege"
+\dP "no.such.schema"."no.such.partitioned.relation"
+\drds "no.such.schema"."no.such.setting"
+\dRp "no.such.schema"."no.such.publication"
+\dRs "no.such.schema"."no.such.subscription"
+\dT "no.such.schema"."no.such.data.type"
+\dx "no.such.schema"."no.such.installed.extension"
+\dX "no.such.schema"."no.such.extended.statistics"
+\dy "no.such.schema"."no.such.event.trigger"
+
+-- again, but with current database and dotted schema qualifications.
+\dt regression."no.such.schema"."no.such.table.relation"
+\da regression."no.such.schema"."no.such.aggregate.function"
+\dc regression."no.such.schema"."no.such.conversion"
+\dC regression."no.such.schema"."no.such.cast"
+\dd regression."no.such.schema"."no.such.object.description"
+\dD regression."no.such.schema"."no.such.domain"
+\di regression."no.such.schema"."no.such.index.relation"
+\dm regression."no.such.schema"."no.such.materialized.view"
+\ds regression."no.such.schema"."no.such.relation"
+\dt regression."no.such.schema"."no.such.relation"
+\dv regression."no.such.schema"."no.such.relation"
+\df regression."no.such.schema"."no.such.function"
+\dF regression."no.such.schema"."no.such.text.search.configuration"
+\dFd regression."no.such.schema"."no.such.text.search.dictionary"
+\dFp regression."no.such.schema"."no.such.text.search.parser"
+\dFt regression."no.such.schema"."no.such.text.search.template"
+\do regression."no.such.schema"."no.such.operator"
+\dO regression."no.such.schema"."no.such.collation"
+\dp regression."no.such.schema"."no.such.access.privilege"
+\dP regression."no.such.schema"."no.such.partitioned.relation"
+\dT regression."no.such.schema"."no.such.data.type"
+\dX regression."no.such.schema"."no.such.extended.statistics"
+
+-- again, but with dotted database and dotted schema qualifications.
+\dt "no.such.database"."no.such.schema"."no.such.table.relation"
+\da "no.such.database"."no.such.schema"."no.such.aggregate.function"
+\dc "no.such.database"."no.such.schema"."no.such.conversion"
+\dC "no.such.database"."no.such.schema"."no.such.cast"
+\dd "no.such.database"."no.such.schema"."no.such.object.description"
+\dD "no.such.database"."no.such.schema"."no.such.domain"
+\ddp "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+\di "no.such.database"."no.such.schema"."no.such.index.relation"
+\dm "no.such.database"."no.such.schema"."no.such.materialized.view"
+\ds "no.such.database"."no.such.schema"."no.such.relation"
+\dt "no.such.database"."no.such.schema"."no.such.relation"
+\dv "no.such.database"."no.such.schema"."no.such.relation"
+\df "no.such.database"."no.such.schema"."no.such.function"
+\dF "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.database"."no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.database"."no.such.schema"."no.such.text.search.template"
+\do "no.such.database"."no.such.schema"."no.such.operator"
+\dO "no.such.database"."no.such.schema"."no.such.collation"
+\dp "no.such.database"."no.such.schema"."no.such.access.privilege"
+\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+\dT "no.such.database"."no.such.schema"."no.such.data.type"
+\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
--
2.35.1
v15-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patchapplication/octet-stream; name=v15-0001-Reject-patterns-with-too-many-parts-or-wrong-db.patch; x-unix-mode=0644Download
From 89a7e61cdfd194857bfe98b0eb4b3bb5db35fdc0 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 18 Apr 2022 10:54:18 -0700
Subject: [PATCH v15] Reject patterns with too many parts or wrong db
Object name patterns used by pg_dump and psql potentially contain
multiple parts (dotted names), and nothing prevents users from
specifying a name with too many parts, nor specifying a
database-qualified name for a database other than the currently
connected database. Prior to PostgreSQL version 14, pg_dump,
pg_dumpall and psql quietly discarded extra parts of the name on the
left. For example, `pg_dump -t` only expected a possibly schema
qualified table name, not a database name, and the following command
pg_dump -t production.marketing.customers
quietly ignored the "production" database name with neither warning
nor error. Commit 2c8726c4b0a496608919d1f78a5abc8c9b6e0868 changed
the behavior of name parsing. Where names contain more than the
maximum expected number of dots, the extra dots on the right were
interpreted as part of the name, such that the above example was
interpreted as schema=production, relation=marketing.customers.
This turns out to be highly unintuitive to users.
We've had reports that users sometimes copy-and-paste database- and
schema-qualified relation names from the logs.
https://www.postgresql.org/message-id/20211013165426.GD27491%40telsasoft.com
There is no support for cross database references, but allowing a
database qualified pattern when the database portion matches the
current database, as in the above report, seems more friendly than
rejecting it, so do that. We don't allow the database portion
itself to be a pattern, because if it matched more than one database
(including the current one), there would be confusion about which
database(s) were processed.
Consistent with how we allow db.schemapat.relpat in pg_dump and psql,
also allow db.schemapat for specifying schemas, as:
\dn mydb.myschema
in psql and
pg_dump --schema=mydb.myschema
Fix the pre-v14 behavior of ignoring leading portions of patterns
containing too many dotted names, and the v14.0 misfeature of
combining trailing portions of such patterns, and instead reject
such patterns in all cases by raising an error.
---
doc/src/sgml/ref/psql-ref.sgml | 17 +-
src/bin/pg_amcheck/pg_amcheck.c | 27 +-
src/bin/pg_amcheck/t/002_nonesuch.pl | 99 +++-
src/bin/pg_dump/pg_dump.c | 65 ++-
src/bin/pg_dump/pg_dumpall.c | 13 +-
src/bin/pg_dump/t/002_pg_dump.pl | 107 ++++
src/bin/psql/describe.c | 504 +++++++++++------
src/fe_utils/string_utils.c | 129 +++--
src/include/fe_utils/string_utils.h | 6 +-
src/test/regress/expected/psql.out | 804 +++++++++++++++++++++++++++
src/test/regress/sql/psql.sql | 242 ++++++++
11 files changed, 1796 insertions(+), 217 deletions(-)
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 592356019b..c6b6ec366c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3641,14 +3641,27 @@ select 1\; select 2\; select 3;
</para>
<para>
- A pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
+ A relation pattern that contains a dot (<literal>.</literal>) is interpreted as a schema
name pattern followed by an object name pattern. For example,
<literal>\dt foo*.*bar*</literal> displays all tables whose table name
includes <literal>bar</literal> that are in schemas whose schema name
starts with <literal>foo</literal>. When no dot appears, then the pattern
matches only objects that are visible in the current schema search path.
Again, a dot within double quotes loses its special meaning and is matched
- literally.
+ literally. A relation pattern that contains two dots (<literal>.</literal>)
+ is interpreted as a database name followed by a schema name pattern followed
+ by an object name pattern. The database name portion will not be treated as
+ a pattern and must match the name of the currently connected database, else
+ an error will be raised.
+ </para>
+
+ <para>
+ A schema pattern that contains a dot (<literal>.</literal>) is interpreted
+ as a database name followed by a schema name pattern. For example,
+ <literal>\dn mydb.*foo*</literal> displays all schemas whose schema name
+ includes <literal>foo</literal>. The database name portion will not be
+ treated as a pattern and must match the name of the currently connected
+ database, else an error will be raised.
</para>
<para>
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 90471e096d..48cee8c1c4 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1308,10 +1308,17 @@ static void
append_database_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData buf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&buf);
- patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false);
+ patternToSQLRegex(encoding, NULL, NULL, &buf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
info->db_regex = pstrdup(buf.data);
@@ -1332,12 +1339,19 @@ append_schema_pattern(PatternInfoArray *pia, const char *pattern, int encoding)
{
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
- patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false);
+ patternToSQLRegex(encoding, NULL, &dbbuf, &nspbuf, pattern, false, false,
+ &dotcnt);
+ if (dotcnt > 1)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
@@ -1369,13 +1383,20 @@ append_relation_pattern_helper(PatternInfoArray *pia, const char *pattern,
PQExpBufferData dbbuf;
PQExpBufferData nspbuf;
PQExpBufferData relbuf;
+ int dotcnt;
PatternInfo *info = extend_pattern_info_array(pia);
initPQExpBuffer(&dbbuf);
initPQExpBuffer(&nspbuf);
initPQExpBuffer(&relbuf);
- patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false);
+ patternToSQLRegex(encoding, &dbbuf, &nspbuf, &relbuf, pattern, false,
+ false, &dotcnt);
+ if (dotcnt > 2)
+ {
+ pg_log_error("improper relation name (too many dotted names): %s", pattern);
+ exit(2);
+ }
info->pattern = pattern;
if (dbbuf.data[0])
{
diff --git a/src/bin/pg_amcheck/t/002_nonesuch.pl b/src/bin/pg_amcheck/t/002_nonesuch.pl
index 56d55199f8..6c0f97027d 100644
--- a/src/bin/pg_amcheck/t/002_nonesuch.pl
+++ b/src/bin/pg_amcheck/t/002_nonesuch.pl
@@ -147,6 +147,100 @@ $node->command_checks_all(
[qr/pg_amcheck: error: no heap tables to check matching "\."/],
'checking table pattern "."');
+# Check that a multipart database name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-d', 'localhost.postgres' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/
+ ],
+ 'multipart database patterns are rejected'
+);
+
+# Check that a three-part schema name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-s', 'localhost.postgres.pg_catalog' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/
+ ],
+ 'three part schema patterns are rejected'
+);
+
+# Check that a four-part table name is rejected
+$node->command_checks_all(
+ [ 'pg_amcheck', '-t', 'localhost.postgres.pg_catalog.pg_class' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/
+ ],
+ 'four part table patterns are rejected'
+);
+
+# Check that too many dotted names still draws an error under --no-strict-names
+# That flag means that it is ok for the object to be missing, not that it is ok
+# for the object name to be ungrammatical
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-t', 'this.is.a.really.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): this\.is\.a\.really\.long\.dotted\.string/
+ ],
+ 'ungrammatical table names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-s', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical schema names still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-d', 'postgres.long.dotted.string' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/
+ ],
+ 'ungrammatical database names still draw errors under --no-strict-names'
+);
+
+# Likewise for exclusion patterns
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-T', 'a.b.c.d' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper relation name \(too many dotted names\): a\.b\.c\.d/
+ ],
+ 'ungrammatical table exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-S', 'a.b.c' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b\.c/
+ ],
+ 'ungrammatical schema exclusions still draw errors under --no-strict-names'
+);
+$node->command_checks_all(
+ [ 'pg_amcheck', '--no-strict-names', '-D', 'a.b' ],
+ 2,
+ [qr/^$/],
+ [
+ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b/
+ ],
+ 'ungrammatical database exclusions still draw errors under --no-strict-names'
+);
+
+
#########################################
# Test checking non-existent databases, schemas, tables, and indexes
@@ -165,9 +259,7 @@ $node->command_checks_all(
'-d', 'no*such*database',
'-r', 'none.none',
'-r', 'none.none.none',
- '-r', 'this.is.a.really.long.dotted.string',
'-r', 'postgres.none.none',
- '-r', 'postgres.long.dotted.string',
'-r', 'postgres.pg_catalog.none',
'-r', 'postgres.none.pg_class',
'-t', 'postgres.pg_catalog.pg_class', # This exists
@@ -186,15 +278,12 @@ $node->command_checks_all(
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no relations to check matching "none\.none"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/,
- qr/pg_amcheck: warning: no relations to check matching "postgres\.long\.dotted\.string"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/,
qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/,
qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/,
- qr/pg_amcheck: warning: no connectable databases to check matching "this\.is\.a\.really\.long\.dotted\.string"/,
],
'many unmatched patterns and one matched pattern under --no-strict-names'
);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 969e2a7a46..d3588607e7 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -178,6 +178,9 @@ static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids,
bool strict_names);
+static void prohibit_crossdb_refs(PGconn *conn, const char *dbname,
+ const char *pattern);
+
static NamespaceInfo *findNamespace(Oid nsoid);
static void dumpTableData(Archive *fout, const TableDataInfo *tdinfo);
static void refreshMatViewData(Archive *fout, const TableDataInfo *tdinfo);
@@ -1315,10 +1318,21 @@ expand_schema_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_namespace n\n");
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "n.nspname", NULL, NULL);
+ false, NULL, "n.nspname", NULL, NULL, &dbbuf,
+ &dotcnt);
+ if (dotcnt > 1)
+ pg_fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 1)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1362,10 +1376,16 @@ expand_extension_name_patterns(Archive *fout,
*/
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_extension e\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "e.extname", NULL, NULL);
+ false, NULL, "e.extname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ pg_fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (strict_names && PQntuples(res) == 0)
@@ -1409,10 +1429,16 @@ expand_foreign_server_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT oid FROM pg_catalog.pg_foreign_server s\n");
processSQLNamePattern(GetConnection(fout), query, cell->val, false,
- false, NULL, "s.srvname", NULL, NULL);
+ false, NULL, "s.srvname", NULL, NULL, NULL,
+ &dotcnt);
+ if (dotcnt > 0)
+ pg_fatal("improper qualified name (too many dotted names): %s",
+ cell->val);
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
if (PQntuples(res) == 0)
@@ -1455,6 +1481,9 @@ expand_table_name_patterns(Archive *fout,
for (cell = patterns->head; cell; cell = cell->next)
{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+
/*
* Query must remain ABSOLUTELY devoid of unqualified names. This
* would be unnecessary given a pg_table_is_visible() variant taking a
@@ -1470,9 +1499,17 @@ expand_table_name_patterns(Archive *fout,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE);
+ initPQExpBuffer(&dbbuf);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ "pg_catalog.pg_table_is_visible(c.oid)", &dbbuf,
+ &dotcnt);
+ if (dotcnt > 2)
+ pg_fatal("improper relation name (too many dotted names): %s",
+ cell->val);
+ else if (dotcnt == 2)
+ prohibit_crossdb_refs(GetConnection(fout), dbbuf.data, cell->val);
+ termPQExpBuffer(&dbbuf);
ExecuteSqlStatement(fout, "RESET search_path");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
@@ -1493,6 +1530,26 @@ expand_table_name_patterns(Archive *fout,
destroyPQExpBuffer(query);
}
+/*
+ * Verifies that the connected database name matches the given database name,
+ * and if not, dies with an error about the given pattern.
+ *
+ * The 'dbname' argument should be a literal name parsed from 'pattern'.
+ */
+static void
+prohibit_crossdb_refs(PGconn *conn, const char *dbname, const char *pattern)
+{
+ const char *db;
+
+ db = PQdb(conn);
+ if (db == NULL)
+ pg_fatal("You are currently not connected to a database.");
+
+ if (strcmp(db, dbname) != 0)
+ pg_fatal("cross-database references are not implemented: %s",
+ pattern);
+}
+
/*
* checkExtensionMembership
* Determine whether object is an extension member, and if so,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 79a723885e..52f9f7c4d6 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -1269,10 +1269,21 @@ expand_dbname_patterns(PGconn *conn,
for (SimpleStringListCell *cell = patterns->head; cell; cell = cell->next)
{
+ int dotcnt;
+
appendPQExpBufferStr(query,
"SELECT datname FROM pg_catalog.pg_database n\n");
processSQLNamePattern(conn, query, cell->val, false,
- false, NULL, "datname", NULL, NULL);
+ false, NULL, "datname", NULL, NULL, NULL,
+ &dotcnt);
+
+ if (dotcnt > 0)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ cell->val);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
res = executeQuery(conn, query->data);
for (int i = 0; i < PQntuples(res); i++)
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c65c92bfb0..1ecfd7ae23 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3973,6 +3973,113 @@ command_fails_like(
qr/\Qpg_dump: error: no matching tables were found for pattern\E/,
'no matching tables');
+#########################################
+# Test invalid multipart database names
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', '.' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \./,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern "."'
+);
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', '.*' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \.\*/,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern ".*"'
+);
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', '*.*' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): \*\.\*/,
+ 'pg_dumpall: option --exclude-database rejects multipart pattern "*.*"'
+);
+
+$node->command_fails_like(
+ [ 'pg_dumpall', '--exclude-database', 'myhost.mydb' ],
+ qr/pg_dumpall: error: improper qualified name \(too many dotted names\): myhost\.mydb/,
+ 'pg_dumpall: option --exclude-database rejects multipart database names'
+);
+
+#########################################
+# Test valid database exclusion patterns
+
+$node->command_ok(
+ [ 'pg_dumpall', '-p', "$port", '--exclude-database', '"myhost.mydb"' ],
+ 'pg_dumpall: option --exclude-database handles database names with embedded dots'
+);
+
+$node->command_ok(
+ [ 'pg_dumpall', '--exclude-database', '??*' ],
+ 'pg_dumpall: option --exclude-database handles database name patterns'
+);
+
+
+#########################################
+# Test invalid multipart schema names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'myhost.mydb.myschema' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): myhost\.mydb\.myschema/,
+ 'pg_dump: option --schema rejects three-part schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', 'otherdb.myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '.' ],
+ qr/pg_dump: error: cross-database references are not implemented: \./,
+ 'pg_dump: option --schema rejects degenerate two-part schema name: "."'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '"some.other.db".myschema' ],
+ qr/pg_dump: error: cross-database references are not implemented: "some\.other\.db"\.myschema/,
+ 'pg_dump: option --schema rejects cross-database multipart schema names with embedded dots'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '.*' ],
+ qr/pg_dump: error: cross-database references are not implemented: \.\*/,
+ 'pg_dump: option --schema rejects degenerate two-part schema name: ".*"'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '..' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): \.\./,
+ 'pg_dump: option --schema rejects degenerate three-part schema name: ".."'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--schema', '.*.*' ],
+ qr/pg_dump: error: improper qualified name \(too many dotted names\): \.\*\.\*/,
+ 'pg_dump: option --schema rejects degenerate three-part schema pattern: ".*.*"'
+);
+
+#########################################
+# Test invalid multipart relation names
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'myhost.mydb.myschema.mytable' ],
+ qr/pg_dump: error: improper relation name \(too many dotted names\): myhost\.mydb\.myschema\.mytable/,
+ 'pg_dump: option --table rejects four-part table names'
+);
+
+$node->command_fails_like(
+ [ 'pg_dump', '--table', 'otherdb.pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: otherdb\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names'
+);
+
+command_fails_like(
+ [ 'pg_dump', '-p', "$port", '--table', '"some.other.db".pg_catalog.pg_class' ],
+ qr/pg_dump: error: cross-database references are not implemented: "some\.other\.db"\.pg_catalog\.pg_class/,
+ 'pg_dump: option --table rejects cross-database three part table names with embedded dots'
+);
+
#########################################
# Run all runs
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 583817b0cc..4369f2235b 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -46,6 +46,12 @@ static bool describeOneTSConfig(const char *oid, const char *nspname,
const char *pnspname, const char *prsname);
static void printACLColumn(PQExpBuffer buf, const char *colname);
static bool listOneExtensionContents(const char *extname, const char *oid);
+static bool validateSQLNamePattern(PQExpBuffer buf, const char *pattern,
+ bool have_where, bool force_escape,
+ const char *schemavar, const char *namevar,
+ const char *altnamevar,
+ const char *visibilityrule,
+ bool *added_clause, int maxparts);
/*----------------
@@ -102,9 +108,11 @@ describeAggregates(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -170,9 +178,11 @@ describeAccessMethods(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_am\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "amname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "amname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -230,9 +240,11 @@ describeTablespaces(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_tablespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "spcname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "spcname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -518,9 +530,11 @@ describeFunctions(const char *functypes, const char *func_pattern,
appendPQExpBufferStr(&buf, " )\n");
}
- processSQLNamePattern(pset.db, &buf, func_pattern, have_where, false,
- "n.nspname", "p.proname", NULL,
- "pg_catalog.pg_function_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, func_pattern, have_where, false,
+ "n.nspname", "p.proname", NULL,
+ "pg_catalog.pg_function_is_visible(p.oid)",
+ NULL, 3))
+ return false;
for (int i = 0; i < num_arg_patterns; i++)
{
@@ -542,10 +556,12 @@ describeFunctions(const char *functypes, const char *func_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -660,11 +676,13 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
" AND n.nspname <> 'information_schema'\n");
/* Match name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, map_typename_pattern(pattern),
- true, false,
- "n.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, map_typename_pattern(pattern),
+ true, false,
+ "n.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -813,10 +831,12 @@ describeOperators(const char *oper_pattern,
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, oper_pattern,
- !showSystem && !oper_pattern, true,
- "n.nspname", "o.oprname", NULL,
- "pg_catalog.pg_operator_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, oper_pattern,
+ !showSystem && !oper_pattern, true,
+ "n.nspname", "o.oprname", NULL,
+ "pg_catalog.pg_operator_is_visible(o.oid)",
+ NULL, 3))
+ return false;
if (num_arg_patterns == 1)
appendPQExpBufferStr(&buf, " AND o.oprleft = 0\n");
@@ -841,10 +861,12 @@ describeOperators(const char *oper_pattern,
"pg_catalog.format_type(t%d.oid, NULL)", i);
snprintf(tiv, sizeof(tiv),
"pg_catalog.pg_type_is_visible(t%d.oid)", i);
- processSQLNamePattern(pset.db, &buf,
- map_typename_pattern(arg_patterns[i]),
- true, false,
- nspname, typname, ft, tiv);
+ if (!validateSQLNamePattern(&buf,
+ map_typename_pattern(arg_patterns[i]),
+ true, false,
+ nspname, typname, ft, tiv,
+ NULL, 3))
+ return false;
}
else
{
@@ -928,8 +950,10 @@ listAllDbs(const char *pattern, bool verbose)
" JOIN pg_catalog.pg_tablespace t on d.dattablespace = t.oid\n");
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
res = PSQLexec(buf.data);
@@ -1078,9 +1102,11 @@ permissionsList(const char *pattern)
* point of view. You can see 'em by explicit request though, eg with \z
* pg_catalog.*
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "n.nspname !~ '^pg_' AND pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -1145,11 +1171,13 @@ listDefaultACLs(const char *pattern)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_default_acl d\n"
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL,
- "n.nspname",
- "pg_catalog.pg_get_userbyid(d.defaclrole)",
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL,
+ "n.nspname",
+ "pg_catalog.pg_get_userbyid(d.defaclrole)",
+ NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 3;");
@@ -1221,9 +1249,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Domain constraint descriptions */
appendPQExpBuffer(&buf,
@@ -1243,9 +1273,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern,
- false, "n.nspname", "pgc.conname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern,
+ false, "n.nspname", "pgc.conname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
/* Operator class descriptions */
appendPQExpBuffer(&buf,
@@ -1265,9 +1297,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "o.opcname", NULL,
- "pg_catalog.pg_opclass_is_visible(o.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "o.opcname", NULL,
+ "pg_catalog.pg_opclass_is_visible(o.oid)",
+ NULL, 3))
+ return false;
/* Operator family descriptions */
appendPQExpBuffer(&buf,
@@ -1287,9 +1321,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "opf.opfname", NULL,
- "pg_catalog.pg_opfamily_is_visible(opf.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "opf.opfname", NULL,
+ "pg_catalog.pg_opfamily_is_visible(opf.oid)",
+ NULL, 3))
+ return false;
/* Rule descriptions (ignore rules for views) */
appendPQExpBuffer(&buf,
@@ -1308,9 +1344,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "r.rulename", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "r.rulename", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
/* Trigger descriptions */
appendPQExpBuffer(&buf,
@@ -1328,9 +1366,11 @@ objectDescription(const char *pattern, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "t.tgname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "t.tgname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf,
") AS tt\n"
@@ -1384,9 +1424,11 @@ describeTableDetails(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "WHERE n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, !showSystem && !pattern, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, !showSystem && !pattern, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2, 3;");
@@ -3572,8 +3614,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE r.rolname !~ '^pg_'\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -3696,10 +3740,13 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
gettext_noop("Role"),
gettext_noop("Database"),
gettext_noop("Settings"));
- havewhere = processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "r.rolname", NULL, NULL);
- processSQLNamePattern(pset.db, &buf, pattern2, havewhere, false,
- NULL, "d.datname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "r.rolname", NULL, NULL, &havewhere, 1))
+ return false;
+ if (!validateSQLNamePattern(&buf, pattern2, havewhere, false,
+ NULL, "d.datname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
res = PSQLexec(buf.data);
@@ -3892,9 +3939,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1,2;");
@@ -4107,9 +4156,11 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
" AND n.nspname !~ '^pg_toast'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBuffer(&buf, "ORDER BY \"Schema\", %s%s\"Name\";",
mixed_output ? "\"Type\" DESC, " : "",
@@ -4182,8 +4233,10 @@ listLanguages(const char *pattern, bool verbose, bool showSystem)
gettext_noop("Description"));
if (pattern)
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "l.lanname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "l.lanname", NULL, NULL,
+ NULL, 2))
+ return false;
if (!showSystem && !pattern)
appendPQExpBufferStr(&buf, "WHERE l.lanplcallfoid != 0\n");
@@ -4265,9 +4318,11 @@ listDomains(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "t.typname", NULL,
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "t.typname", NULL,
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4339,9 +4394,11 @@ listConversions(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n"
" AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.conname", NULL,
- "pg_catalog.pg_conversion_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.conname", NULL,
+ "pg_catalog.pg_conversion_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4406,7 +4463,7 @@ describeConfigurationParameters(const char *pattern, bool verbose,
processSQLNamePattern(pset.db, &buf, pattern,
false, false,
NULL, "pg_catalog.lower(s.name)", NULL,
- NULL);
+ NULL, NULL, NULL);
else
appendPQExpBufferStr(&buf, "WHERE s.source <> 'default' AND\n"
" s.setting IS DISTINCT FROM s.boot_val\n");
@@ -4485,8 +4542,10 @@ listEventTriggers(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_event_trigger e ");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "evtname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1");
@@ -4577,10 +4636,12 @@ listExtendedStats(const char *pattern)
appendPQExpBufferStr(&buf,
" \nFROM pg_catalog.pg_statistic_ext es \n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
- NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)");
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ "es.stxnamespace::pg_catalog.regnamespace::pg_catalog.text", "es.stxname",
+ NULL, "pg_catalog.pg_statistics_obj_is_visible(es.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4679,17 +4740,21 @@ listCasts(const char *pattern, bool verbose)
* Match name pattern against either internal or external name of either
* castsource or casttarget
*/
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "ns.nspname", "ts.typname",
- "pg_catalog.format_type(ts.oid, NULL)",
- "pg_catalog.pg_type_is_visible(ts.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "ns.nspname", "ts.typname",
+ "pg_catalog.format_type(ts.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(ts.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") OR (true");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "nt.nspname", "tt.typname",
- "pg_catalog.format_type(tt.oid, NULL)",
- "pg_catalog.pg_type_is_visible(tt.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "nt.nspname", "tt.typname",
+ "pg_catalog.format_type(tt.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(tt.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, ") )\nORDER BY 1, 2;");
@@ -4785,9 +4850,11 @@ listCollations(const char *pattern, bool verbose, bool showSystem)
*/
appendPQExpBufferStr(&buf, " AND c.collencoding IN (-1, pg_catalog.pg_char_to_encoding(pg_catalog.getdatabaseencoding()))\n");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.collname", NULL,
- "pg_catalog.pg_collation_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.collname", NULL,
+ "pg_catalog.pg_collation_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -4845,10 +4912,12 @@ listSchemas(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf,
"WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- !showSystem && !pattern, false,
- NULL, "n.nspname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ !showSystem && !pattern, false,
+ NULL, "n.nspname", NULL,
+ NULL,
+ NULL, 2))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -4959,9 +5028,11 @@ listTSParsers(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5000,9 +5071,11 @@ listTSParsersVerbose(const char *pattern)
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "p.prsname", NULL,
- "pg_catalog.pg_ts_parser_is_visible(p.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "p.prsname", NULL,
+ "pg_catalog.pg_ts_parser_is_visible(p.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5207,9 +5280,11 @@ listTSDictionaries(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_dict d\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "d.dictname", NULL,
- "pg_catalog.pg_ts_dict_is_visible(d.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "d.dictname", NULL,
+ "pg_catalog.pg_ts_dict_is_visible(d.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5268,9 +5343,11 @@ listTSTemplates(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "FROM pg_catalog.pg_ts_template t\n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "t.tmplname", NULL,
- "pg_catalog.pg_ts_template_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "t.tmplname", NULL,
+ "pg_catalog.pg_ts_template_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5318,9 +5395,11 @@ listTSConfigs(const char *pattern, bool verbose)
gettext_noop("Description")
);
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5360,9 +5439,11 @@ listTSConfigsVerbose(const char *pattern)
"WHERE p.oid = c.cfgparser\n"
);
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- "n.nspname", "c.cfgname", NULL,
- "pg_catalog.pg_ts_config_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ "n.nspname", "c.cfgname", NULL,
+ "pg_catalog.pg_ts_config_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 3, 2;");
@@ -5532,8 +5613,10 @@ listForeignDataWrappers(const char *pattern, bool verbose)
" ON d.classoid = fdw.tableoid "
"AND d.objoid = fdw.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "fdwname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "fdwname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5604,8 +5687,10 @@ listForeignServers(const char *pattern, bool verbose)
"ON d.classoid = s.tableoid AND d.objoid = s.oid "
"AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "s.srvname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "s.srvname", NULL, NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5655,8 +5740,10 @@ listUserMappings(const char *pattern, bool verbose)
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_user_mappings um\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "um.srvname", "um.usename", NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "um.srvname", "um.usename", NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5722,9 +5809,11 @@ listForeignTables(const char *pattern, bool verbose)
" ON d.classoid = c.tableoid AND "
"d.objoid = c.oid AND d.objsubid = 0\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- "n.nspname", "c.relname", NULL,
- "pg_catalog.pg_table_is_visible(c.oid)");
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ "n.nspname", "c.relname", NULL,
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2;");
@@ -5768,10 +5857,12 @@ listExtensions(const char *pattern)
gettext_noop("Schema"),
gettext_noop("Description"));
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5807,10 +5898,12 @@ listExtensionContents(const char *pattern)
"SELECT e.extname, e.oid\n"
"FROM pg_catalog.pg_extension e\n");
- processSQLNamePattern(pset.db, &buf, pattern,
- false, false,
- NULL, "e.extname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern,
+ false, false,
+ NULL, "e.extname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -5892,6 +5985,59 @@ listOneExtensionContents(const char *extname, const char *oid)
return true;
}
+/*
+ * validateSQLNamePattern
+ *
+ * Wrapper around string_utils's processSQLNamePattern which also checks the
+ * pattern's validity. In addition to that function's parameters, takes a
+ * 'maxparts' parameter specifying the maximum number of dotted names the
+ * pattern is allowed to have, and a 'added_clause' parameter that returns by
+ * reference whether a clause was added to 'buf'. Returns whether the pattern
+ * passed validation, after logging any errors.
+ */
+static bool
+validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where,
+ bool force_escape, const char *schemavar,
+ const char *namevar, const char *altnamevar,
+ const char *visibilityrule, bool *added_clause,
+ int maxparts)
+{
+ PQExpBufferData dbbuf;
+ int dotcnt;
+ bool added;
+
+ initPQExpBuffer(&dbbuf);
+ added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape,
+ schemavar, namevar, altnamevar,
+ visibilityrule, &dbbuf, &dotcnt);
+ if (added_clause != NULL)
+ *added_clause = added;
+
+ if (dotcnt >= maxparts)
+ {
+ pg_log_error("improper qualified name (too many dotted names): %s",
+ pattern);
+ termPQExpBuffer(&dbbuf);
+ return false;
+ }
+
+ if (maxparts > 1 && dotcnt == maxparts-1)
+ {
+ if (PQdb(pset.db) == NULL)
+ {
+ pg_log_error("You are currently not connected to a database.");
+ return false;
+ }
+ if (strcmp(PQdb(pset.db), dbbuf.data) != 0)
+ {
+ pg_log_error("cross-database references are not implemented: %s",
+ pattern);
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* \dRp
* Lists publications.
@@ -5943,9 +6089,11 @@ listPublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6056,9 +6204,11 @@ describePublications(const char *pattern)
appendPQExpBufferStr(&buf,
"\nFROM pg_catalog.pg_publication\n");
- processSQLNamePattern(pset.db, &buf, pattern, false, false,
- NULL, "pubname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, false, false,
+ NULL, "pubname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 2;");
@@ -6266,9 +6416,11 @@ describeSubscriptions(const char *pattern, bool verbose)
" FROM pg_catalog.pg_database\n"
" WHERE datname = pg_catalog.current_database())");
- processSQLNamePattern(pset.db, &buf, pattern, true, false,
- NULL, "subname", NULL,
- NULL);
+ if (!validateSQLNamePattern(&buf, pattern, true, false,
+ NULL, "subname", NULL,
+ NULL,
+ NULL, 1))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1;");
@@ -6369,15 +6521,19 @@ listOperatorClasses(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace ofn ON ofn.oid = of.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, have_where, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, have_where, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
}
appendPQExpBufferStr(&buf, "ORDER BY 1, 2, 4;");
@@ -6441,8 +6597,10 @@ listOperatorFamilies(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = f.opfnamespace\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname", NULL, NULL,
+ &have_where, 1))
+ return false;
if (type_pattern)
{
appendPQExpBuffer(&buf,
@@ -6454,10 +6612,12 @@ listOperatorFamilies(const char *access_method_pattern,
" WHERE oc.opcfamily = f.oid\n",
have_where ? "AND" : "WHERE");
/* Match type name pattern against either internal or external name */
- processSQLNamePattern(pset.db, &buf, type_pattern, true, false,
- "tn.nspname", "t.typname",
- "pg_catalog.format_type(t.oid, NULL)",
- "pg_catalog.pg_type_is_visible(t.oid)");
+ if (!validateSQLNamePattern(&buf, type_pattern, true, false,
+ "tn.nspname", "t.typname",
+ "pg_catalog.format_type(t.oid, NULL)",
+ "pg_catalog.pg_type_is_visible(t.oid)",
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, " )\n");
}
@@ -6535,13 +6695,17 @@ listOpFamilyOperators(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_opfamily ofs ON ofs.oid = o.amopsortfamily\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "nsf.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "nsf.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" o.amoplefttype = o.amoprighttype DESC,\n"
@@ -6619,12 +6783,16 @@ listOpFamilyFunctions(const char *access_method_pattern,
" LEFT JOIN pg_catalog.pg_proc p ON ap.amproc = p.oid\n");
if (access_method_pattern)
- have_where = processSQLNamePattern(pset.db, &buf, access_method_pattern,
- false, false, NULL, "am.amname",
- NULL, NULL);
+ if (!validateSQLNamePattern(&buf, access_method_pattern,
+ false, false, NULL, "am.amname",
+ NULL, NULL,
+ &have_where, 1))
+ return false;
if (family_pattern)
- processSQLNamePattern(pset.db, &buf, family_pattern, have_where, false,
- "ns.nspname", "of.opfname", NULL, NULL);
+ if (!validateSQLNamePattern(&buf, family_pattern, have_where, false,
+ "ns.nspname", "of.opfname", NULL, NULL,
+ NULL, 3))
+ return false;
appendPQExpBufferStr(&buf, "ORDER BY 1, 2,\n"
" ap.amproclefttype = ap.amprocrighttype DESC,\n"
diff --git a/src/fe_utils/string_utils.c b/src/fe_utils/string_utils.c
index 1c61840462..c3ea4fc186 100644
--- a/src/fe_utils/string_utils.c
+++ b/src/fe_utils/string_utils.c
@@ -882,6 +882,9 @@ appendReloptionsArray(PQExpBuffer buffer, const char *reloptions,
* altnamevar: NULL, or name of an alternative variable to match against name.
* visibilityrule: clause to use if we want to restrict to visible objects
* (for example, "pg_catalog.pg_table_is_visible(p.oid)"). Can be NULL.
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * dotcnt: how many separators were parsed from the pattern, by reference.
*
* Formatting note: the text already present in buf should end with a newline.
* The appended text, if any, will end with one too.
@@ -890,16 +893,21 @@ bool
processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule)
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt)
{
PQExpBufferData schemabuf;
PQExpBufferData namebuf;
bool added_clause = false;
+ int dcnt;
#define WHEREAND() \
(appendPQExpBufferStr(buf, have_where ? " AND " : "WHERE "), \
have_where = true, added_clause = true)
+ if (dotcnt == NULL)
+ dotcnt = &dcnt;
+ *dotcnt = 0;
if (pattern == NULL)
{
/* Default: select all visible objects */
@@ -922,9 +930,11 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the caller provided a schemavar, we want to split the pattern on
* ".", otherwise not.
*/
- patternToSQLRegex(PQclientEncoding(conn), NULL,
- (schemavar ? &schemabuf : NULL), &namebuf,
- pattern, force_escape);
+ patternToSQLRegex(PQclientEncoding(conn),
+ (schemavar ? dbnamebuf : NULL),
+ (schemavar ? &schemabuf : NULL),
+ &namebuf,
+ pattern, force_escape, true, dotcnt);
/*
* Now decide what we need to emit. We may run under a hostile
@@ -937,7 +947,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* is >= v12 then we need to force it through explicit COLLATE clauses,
* otherwise the "C" collation attached to "name" catalog columns wins.
*/
- if (namebuf.len > 2)
+ if (namevar && namebuf.len > 2)
{
/* We have a name pattern, so constrain the namevar(s) */
@@ -971,7 +981,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
}
}
- if (schemabuf.len > 2)
+ if (schemavar && schemabuf.len > 2)
{
/* We have a schema pattern, so constrain the schemavar */
@@ -1012,8 +1022,7 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
* If the dbnamebuf and schemabuf arguments are non-NULL, and the pattern
* contains two or more dbname/schema/name separators, we parse the portions of
* the pattern prior to the first and second separators into dbnamebuf and
- * schemabuf, and the rest into namebuf. (Additional dots in the name portion
- * are not treated as special.)
+ * schemabuf, and the rest into namebuf.
*
* If dbnamebuf is NULL and schemabuf is non-NULL, and the pattern contains at
* least one separator, we parse the first portion into schemabuf and the rest
@@ -1021,24 +1030,49 @@ processSQLNamePattern(PGconn *conn, PQExpBuffer buf, const char *pattern,
*
* Otherwise, we parse all the pattern into namebuf.
*
+ * If the pattern contains more dotted parts than buffers to parse into, the
+ * extra dots will be treated as literal characters and written into the
+ * namebuf, though they will be counted. Callers should always check the value
+ * returned by reference in dotcnt and handle this error case appropriately.
+ *
* We surround the regexps with "^(...)$" to force them to match whole strings,
* as per SQL practice. We have to have parens in case strings contain "|",
* else the "^" and "$" will be bound into the first and last alternatives
- * which is not what we want.
+ * which is not what we want. Whether this is done for dbnamebuf is controlled
+ * by the want_literal_dbname parameter.
*
* The regexps we parse into the buffers are appended to the data (if any)
* already present. If we parse fewer fields than the number of buffers we
* were given, the extra buffers are unaltered.
+ *
+ * encoding: the character encoding for the given pattern
+ * dbnamebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * schemabuf: output parameter receiving the schema name portion of the
+ * pattern, if any. Can be NULL.
+ * namebuf: output parameter receiving the database name portion of the
+ * pattern, if any. Can be NULL.
+ * pattern: user-specified pattern option, or NULL if none ("*" is implied).
+ * force_escape: always quote regexp special characters, even outside
+ * double quotes (else they are quoted only between double quotes).
+ * want_literal_dbname: if true, regexp special characters within the database
+ * name portion of the pattern will not be escaped, nor will the dbname be
+ * converted into a regular expression.
+ * dotcnt: output parameter receiving the number of separators parsed from the
+ * pattern.
*/
void
patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
- PQExpBuffer namebuf, const char *pattern, bool force_escape)
+ PQExpBuffer namebuf, const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt)
{
PQExpBufferData buf[3];
+ PQExpBufferData left_literal;
PQExpBuffer curbuf;
PQExpBuffer maxbuf;
int i;
bool inquotes;
+ bool left;
const char *cp;
Assert(pattern != NULL);
@@ -1046,7 +1080,9 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
/* callers should never expect "dbname.relname" format */
Assert(dbnamebuf == NULL || schemabuf != NULL);
+ Assert(dotcnt != NULL);
+ *dotcnt = 0;
inquotes = false;
cp = pattern;
@@ -1058,6 +1094,13 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
maxbuf = &buf[0];
curbuf = &buf[0];
+ if (want_literal_dbname)
+ {
+ left = true;
+ initPQExpBuffer(&left_literal);
+ }
+ else
+ left = false;
initPQExpBuffer(curbuf);
appendPQExpBufferStr(curbuf, "^(");
while (*cp)
@@ -1070,6 +1113,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
/* emit one quote, stay in inquotes mode */
appendPQExpBufferChar(curbuf, '"');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '"');
cp++;
}
else
@@ -1080,32 +1125,40 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
{
appendPQExpBufferChar(curbuf,
pg_tolower((unsigned char) ch));
+ if (left)
+ appendPQExpBufferChar(&left_literal,
+ pg_tolower((unsigned char) ch));
cp++;
}
else if (!inquotes && ch == '*')
{
appendPQExpBufferStr(curbuf, ".*");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '*');
cp++;
}
else if (!inquotes && ch == '?')
{
appendPQExpBufferChar(curbuf, '.');
+ if (left)
+ appendPQExpBufferChar(&left_literal, '?');
cp++;
}
-
- /*
- * When we find a dbname/schema/name separator, we treat it specially
- * only if the caller requested more patterns to be parsed than we
- * have already parsed from the pattern. Otherwise, dot characters
- * are not special.
- */
- else if (!inquotes && ch == '.' && curbuf < maxbuf)
+ else if (!inquotes && ch == '.')
{
- appendPQExpBufferStr(curbuf, ")$");
- curbuf++;
- initPQExpBuffer(curbuf);
- appendPQExpBufferStr(curbuf, "^(");
- cp++;
+ left = false;
+ if (dotcnt)
+ (*dotcnt)++;
+ if (curbuf < maxbuf)
+ {
+ appendPQExpBufferStr(curbuf, ")$");
+ curbuf++;
+ initPQExpBuffer(curbuf);
+ appendPQExpBufferStr(curbuf, "^(");
+ cp++;
+ }
+ else
+ appendPQExpBufferChar(curbuf, *cp++);
}
else if (ch == '$')
{
@@ -1117,6 +1170,8 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
* having it possess its regexp meaning.
*/
appendPQExpBufferStr(curbuf, "\\$");
+ if (left)
+ appendPQExpBufferChar(&left_literal, '$');
cp++;
}
else
@@ -1141,25 +1196,35 @@ patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf, PQExpBuffer schemabuf,
appendPQExpBufferChar(curbuf, '\\');
i = PQmblenBounded(cp, encoding);
while (i--)
+ {
+ if (left)
+ appendPQExpBufferChar(&left_literal, *cp);
appendPQExpBufferChar(curbuf, *cp++);
+ }
}
}
appendPQExpBufferStr(curbuf, ")$");
- appendPQExpBufferStr(namebuf, curbuf->data);
- termPQExpBuffer(curbuf);
-
- if (curbuf > buf)
+ if (namebuf)
{
+ appendPQExpBufferStr(namebuf, curbuf->data);
+ termPQExpBuffer(curbuf);
curbuf--;
+ }
+
+ if (schemabuf && curbuf >= buf)
+ {
appendPQExpBufferStr(schemabuf, curbuf->data);
termPQExpBuffer(curbuf);
+ curbuf--;
+ }
- if (curbuf > buf)
- {
- curbuf--;
+ if (dbnamebuf && curbuf >= buf)
+ {
+ if (want_literal_dbname)
+ appendPQExpBufferStr(dbnamebuf, left_literal.data);
+ else
appendPQExpBufferStr(dbnamebuf, curbuf->data);
- termPQExpBuffer(curbuf);
- }
+ termPQExpBuffer(curbuf);
}
}
diff --git a/src/include/fe_utils/string_utils.h b/src/include/fe_utils/string_utils.h
index b9b8708dab..fa4deb2497 100644
--- a/src/include/fe_utils/string_utils.h
+++ b/src/include/fe_utils/string_utils.h
@@ -55,10 +55,12 @@ extern bool processSQLNamePattern(PGconn *conn, PQExpBuffer buf,
const char *pattern,
bool have_where, bool force_escape,
const char *schemavar, const char *namevar,
- const char *altnamevar, const char *visibilityrule);
+ const char *altnamevar, const char *visibilityrule,
+ PQExpBuffer dbnamebuf, int *dotcnt);
extern void patternToSQLRegex(int encoding, PQExpBuffer dbnamebuf,
PQExpBuffer schemabuf, PQExpBuffer namebuf,
- const char *pattern, bool force_escape);
+ const char *pattern, bool force_escape,
+ bool want_literal_dbname, int *dotcnt);
#endif /* STRING_UTILS_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 8e11ebbcaa..1c5b5d2763 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5549,3 +5549,807 @@ SELECT * FROM bla ORDER BY 1;
# final ON_ERROR_ROLLBACK: off
DROP TABLE bla;
DROP FUNCTION psql_error;
+-- check describing invalid multipart names
+\dA regression.heap
+improper qualified name (too many dotted names): regression.heap
+\dA nonesuch.heap
+improper qualified name (too many dotted names): nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+cross-database references are not implemented: |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+improper qualified name (too many dotted names): host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+cross-database references are not implemented: +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+cross-database references are not implemented: nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAc regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAf nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAf regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAo nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAo regression.brin
+improper qualified name (too many dotted names): regression.brin
+\dAp nonesuch.brin
+improper qualified name (too many dotted names): nonesuch.brin
+\dAp regression.brin
+improper qualified name (too many dotted names): regression.brin
+\db nonesuch.pg_default
+improper qualified name (too many dotted names): nonesuch.pg_default
+\db regression.pg_default
+improper qualified name (too many dotted names): regression.pg_default
+\dc host.regression.public.conversion
+improper qualified name (too many dotted names): host.regression.public.conversion
+\dc (.public.conversion
+cross-database references are not implemented: (.public.conversion
+\dc nonesuch.public.conversion
+cross-database references are not implemented: nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+improper qualified name (too many dotted names): host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+cross-database references are not implemented: ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+cross-database references are not implemented: nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+cross-database references are not implemented: [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+improper qualified name (too many dotted names): host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+cross-database references are not implemented: ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+cross-database references are not implemented: nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+improper qualified name (too many dotted names): host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+cross-database references are not implemented: {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+cross-database references are not implemented: nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+improper qualified name (too many dotted names): host.regression.public.ft
+\dE }.public.ft
+cross-database references are not implemented: }.public.ft
+\dE nonesuch.public.ft
+cross-database references are not implemented: nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+improper qualified name (too many dotted names): host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+improper qualified name (too many dotted names): ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+cross-database references are not implemented: nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+improper qualified name (too many dotted names): host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+cross-database references are not implemented: ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+cross-database references are not implemented: nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+improper qualified name (too many dotted names): host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+cross-database references are not implemented: regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+cross-database references are not implemented: nonesuch.public.check_seq
+\dt host.regression.public.b_star
+improper qualified name (too many dotted names): host.regression.public.b_star
+\dt regres+ion.public.b_star
+cross-database references are not implemented: regres+ion.public.b_star
+\dt nonesuch.public.b_star
+cross-database references are not implemented: nonesuch.public.b_star
+\dv host.regression.public.shoe
+improper qualified name (too many dotted names): host.regression.public.shoe
+\dv regress(ion).public.shoe
+cross-database references are not implemented: regress(ion).public.shoe
+\dv nonesuch.public.shoe
+cross-database references are not implemented: nonesuch.public.shoe
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.server
+improper qualified name (too many dotted names): nonesuch.server
+\des regression.server
+improper qualified name (too many dotted names): regression.server
+\des nonesuch.username
+improper qualified name (too many dotted names): nonesuch.username
+\des regression.username
+improper qualified name (too many dotted names): regression.username
+\dew nonesuch.fdw
+improper qualified name (too many dotted names): nonesuch.fdw
+\dew regression.fdw
+improper qualified name (too many dotted names): regression.fdw
+\df host.regression.public.namelen
+improper qualified name (too many dotted names): host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+cross-database references are not implemented: regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+cross-database references are not implemented: nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+cross-database references are not implemented: regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+cross-database references are not implemented: nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+improper qualified name (too many dotted names): host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+cross-database references are not implemented: regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+cross-database references are not implemented: nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+improper qualified name (too many dotted names): host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+cross-database references are not implemented: ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+cross-database references are not implemented: nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+improper qualified name (too many dotted names): host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+cross-database references are not implemented: regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+cross-database references are not implemented: nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+improper qualified name (too many dotted names): nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+improper qualified name (too many dotted names): regression.pg_database_owner
+\dL host.regression.plpgsql
+improper qualified name (too many dotted names): host.regression.plpgsql
+\dL *.plpgsql
+cross-database references are not implemented: *.plpgsql
+\dL nonesuch.plpgsql
+cross-database references are not implemented: nonesuch.plpgsql
+\dn host.regression.public
+improper qualified name (too many dotted names): host.regression.public
+\dn """".public
+cross-database references are not implemented: """".public
+\dn nonesuch.public
+cross-database references are not implemented: nonesuch.public
+\do host.regression.public.!=-
+improper qualified name (too many dotted names): host.regression.public.!=-
+\do "regression|mydb".public.!=-
+cross-database references are not implemented: "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+cross-database references are not implemented: nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+improper qualified name (too many dotted names): host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+cross-database references are not implemented: .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+cross-database references are not implemented: nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+improper qualified name (too many dotted names): host.regression.public.a_star
+\dp "regres+ion".public.a_star
+cross-database references are not implemented: "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+cross-database references are not implemented: nonesuch.public.a_star
+\dP host.regression.public.mlparted
+improper qualified name (too many dotted names): host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+cross-database references are not implemented: "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+cross-database references are not implemented: nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+improper qualified name (too many dotted names): nonesuch.lc_messages
+\drds regression.lc_messages
+improper qualified name (too many dotted names): regression.lc_messages
+\dRp public.mypub
+improper qualified name (too many dotted names): public.mypub
+\dRp regression.mypub
+improper qualified name (too many dotted names): regression.mypub
+\dRs public.mysub
+improper qualified name (too many dotted names): public.mysub
+\dRs regression.mysub
+improper qualified name (too many dotted names): regression.mysub
+\dT host.regression.public.widget
+improper qualified name (too many dotted names): host.regression.public.widget
+\dT "regression{1,2}".public.widget
+cross-database references are not implemented: "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+cross-database references are not implemented: nonesuch.public.widget
+\dx regression.plpgsql
+improper qualified name (too many dotted names): regression.plpgsql
+\dx nonesuch.plpgsql
+improper qualified name (too many dotted names): nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+improper qualified name (too many dotted names): host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+cross-database references are not implemented: "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+cross-database references are not implemented: nonesuch.public.func_deps_stat
+\dy regression.myevt
+improper qualified name (too many dotted names): regression.myevt
+\dy nonesuch.myevt
+improper qualified name (too many dotted names): nonesuch.myevt
+-- check that dots within quoted name segments are not counted
+\dA "no.such.access.method"
+List of access methods
+ Name | Type
+------+------
+(0 rows)
+
+\dt "no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da "no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dAc "no.such.operator.class"
+ List of operator classes
+ AM | Input type | Storage type | Operator class | Default?
+----+------------+--------------+----------------+----------
+(0 rows)
+
+\dAf "no.such.operator.family"
+ List of operator families
+ AM | Operator family | Applicable types
+----+-----------------+------------------
+(0 rows)
+
+\dAo "no.such.operator.of.operator.family"
+ List of operators of operator families
+ AM | Operator family | Operator | Strategy | Purpose
+----+-----------------+----------+----------+---------
+(0 rows)
+
+\dAp "no.such.operator.support.function.of.operator.family"
+ List of support functions of operator families
+ AM | Operator family | Registered left type | Registered right type | Number | Function
+----+-----------------+----------------------+-----------------------+--------+----------
+(0 rows)
+
+\db "no.such.tablespace"
+ List of tablespaces
+ Name | Owner | Location
+------+-------+----------
+(0 rows)
+
+\dc "no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC "no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd "no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD "no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\ddp "no.such.default.access.privilege"
+ Default access privileges
+ Owner | Schema | Type | Access privileges
+-------+--------+------+-------------------
+(0 rows)
+
+\di "no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm "no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv "no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\des "no.such.foreign.server"
+ List of foreign servers
+ Name | Owner | Foreign-data wrapper
+------+-------+----------------------
+(0 rows)
+
+\dew "no.such.foreign.data.wrapper"
+ List of foreign-data wrappers
+ Name | Owner | Handler | Validator
+------+-------+---------+-----------
+(0 rows)
+
+\df "no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF "no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd "no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp "no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt "no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dg "no.such.role"
+ List of roles
+ Role name | Attributes | Member of
+-----------+------------+-----------
+
+\dL "no.such.language"
+ List of languages
+ Name | Owner | Trusted | Description
+------+-------+---------+-------------
+(0 rows)
+
+\dn "no.such.schema"
+List of schemas
+ Name | Owner
+------+-------
+(0 rows)
+
+\do "no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO "no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | ICU Locale | Provider | Deterministic?
+--------+------+---------+-------+------------+----------+----------------
+(0 rows)
+
+\dp "no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP "no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\drds "no.such.setting"
+ List of settings
+ Role | Database | Settings
+------+----------+----------
+(0 rows)
+
+\dRp "no.such.publication"
+ List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root
+------+-------+------------+---------+---------+---------+-----------+----------
+(0 rows)
+
+\dRs "no.such.subscription"
+ List of subscriptions
+ Name | Owner | Enabled | Publication
+------+-------+---------+-------------
+(0 rows)
+
+\dT "no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dx "no.such.installed.extension"
+ List of installed extensions
+ Name | Version | Schema | Description
+------+---------+--------+-------------
+(0 rows)
+
+\dX "no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+\dy "no.such.event.trigger"
+ List of event triggers
+ Name | Event | Owner | Enabled | Function | Tags
+------+-------+-------+---------+----------+------
+(0 rows)
+
+-- again, but with dotted schema qualifications.
+\dA "no.such.schema"."no.such.access.method"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.access.method"
+\dt "no.such.schema"."no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da "no.such.schema"."no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dAc "no.such.schema"."no.such.operator.class"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.class"
+\dAf "no.such.schema"."no.such.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.family"
+\dAo "no.such.schema"."no.such.operator.of.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.of.operator.family"
+\dAp "no.such.schema"."no.such.operator.support.function.of.operator.family"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.operator.support.function.of.operator.family"
+\db "no.such.schema"."no.such.tablespace"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.tablespace"
+\dc "no.such.schema"."no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC "no.such.schema"."no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd "no.such.schema"."no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD "no.such.schema"."no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\ddp "no.such.schema"."no.such.default.access.privilege"
+ Default access privileges
+ Owner | Schema | Type | Access privileges
+-------+--------+------+-------------------
+(0 rows)
+
+\di "no.such.schema"."no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm "no.such.schema"."no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv "no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\des "no.such.schema"."no.such.foreign.server"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.foreign.server"
+\dew "no.such.schema"."no.such.foreign.data.wrapper"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.foreign.data.wrapper"
+\df "no.such.schema"."no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF "no.such.schema"."no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd "no.such.schema"."no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp "no.such.schema"."no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt "no.such.schema"."no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dg "no.such.schema"."no.such.role"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.role"
+\dL "no.such.schema"."no.such.language"
+cross-database references are not implemented: "no.such.schema"."no.such.language"
+\do "no.such.schema"."no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO "no.such.schema"."no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | ICU Locale | Provider | Deterministic?
+--------+------+---------+-------+------------+----------+----------------
+(0 rows)
+
+\dp "no.such.schema"."no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP "no.such.schema"."no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\drds "no.such.schema"."no.such.setting"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.setting"
+\dRp "no.such.schema"."no.such.publication"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.publication"
+\dRs "no.such.schema"."no.such.subscription"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.subscription"
+\dT "no.such.schema"."no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dx "no.such.schema"."no.such.installed.extension"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.installed.extension"
+\dX "no.such.schema"."no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+\dy "no.such.schema"."no.such.event.trigger"
+improper qualified name (too many dotted names): "no.such.schema"."no.such.event.trigger"
+-- again, but with current database and dotted schema qualifications.
+\dt regression."no.such.schema"."no.such.table.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\da regression."no.such.schema"."no.such.aggregate.function"
+ List of aggregate functions
+ Schema | Name | Result data type | Argument data types | Description
+--------+------+------------------+---------------------+-------------
+(0 rows)
+
+\dc regression."no.such.schema"."no.such.conversion"
+ List of conversions
+ Schema | Name | Source | Destination | Default?
+--------+------+--------+-------------+----------
+(0 rows)
+
+\dC regression."no.such.schema"."no.such.cast"
+ List of casts
+ Source type | Target type | Function | Implicit?
+-------------+-------------+----------+-----------
+(0 rows)
+
+\dd regression."no.such.schema"."no.such.object.description"
+ Object descriptions
+ Schema | Name | Object | Description
+--------+------+--------+-------------
+(0 rows)
+
+\dD regression."no.such.schema"."no.such.domain"
+ List of domains
+ Schema | Name | Type | Collation | Nullable | Default | Check
+--------+------+------+-----------+----------+---------+-------
+(0 rows)
+
+\di regression."no.such.schema"."no.such.index.relation"
+ List of relations
+ Schema | Name | Type | Owner | Table
+--------+------+------+-------+-------
+(0 rows)
+
+\dm regression."no.such.schema"."no.such.materialized.view"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\ds regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dt regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\dv regression."no.such.schema"."no.such.relation"
+ List of relations
+ Schema | Name | Type | Owner
+--------+------+------+-------
+(0 rows)
+
+\df regression."no.such.schema"."no.such.function"
+ List of functions
+ Schema | Name | Result data type | Argument data types | Type
+--------+------+------------------+---------------------+------
+(0 rows)
+
+\dF regression."no.such.schema"."no.such.text.search.configuration"
+List of text search configurations
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFd regression."no.such.schema"."no.such.text.search.dictionary"
+List of text search dictionaries
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFp regression."no.such.schema"."no.such.text.search.parser"
+ List of text search parsers
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dFt regression."no.such.schema"."no.such.text.search.template"
+List of text search templates
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\do regression."no.such.schema"."no.such.operator"
+ List of operators
+ Schema | Name | Left arg type | Right arg type | Result type | Description
+--------+------+---------------+----------------+-------------+-------------
+(0 rows)
+
+\dO regression."no.such.schema"."no.such.collation"
+ List of collations
+ Schema | Name | Collate | Ctype | ICU Locale | Provider | Deterministic?
+--------+------+---------+-------+------------+----------+----------------
+(0 rows)
+
+\dp regression."no.such.schema"."no.such.access.privilege"
+ Access privileges
+ Schema | Name | Type | Access privileges | Column privileges | Policies
+--------+------+------+-------------------+-------------------+----------
+(0 rows)
+
+\dP regression."no.such.schema"."no.such.partitioned.relation"
+ List of partitioned relations
+ Schema | Name | Owner | Type | Parent name | Table
+--------+------+-------+------+-------------+-------
+(0 rows)
+
+\dT regression."no.such.schema"."no.such.data.type"
+ List of data types
+ Schema | Name | Description
+--------+------+-------------
+(0 rows)
+
+\dX regression."no.such.schema"."no.such.extended.statistics"
+ List of extended statistics
+ Schema | Name | Definition | Ndistinct | Dependencies | MCV
+--------+------+------------+-----------+--------------+-----
+(0 rows)
+
+-- again, but with dotted database and dotted schema qualifications.
+\dt "no.such.database"."no.such.schema"."no.such.table.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.table.relation"
+\da "no.such.database"."no.such.schema"."no.such.aggregate.function"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.aggregate.function"
+\dc "no.such.database"."no.such.schema"."no.such.conversion"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.conversion"
+\dC "no.such.database"."no.such.schema"."no.such.cast"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.cast"
+\dd "no.such.database"."no.such.schema"."no.such.object.description"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.object.description"
+\dD "no.such.database"."no.such.schema"."no.such.domain"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.domain"
+\ddp "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+\di "no.such.database"."no.such.schema"."no.such.index.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.index.relation"
+\dm "no.such.database"."no.such.schema"."no.such.materialized.view"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.materialized.view"
+\ds "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\dt "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\dv "no.such.database"."no.such.schema"."no.such.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.relation"
+\df "no.such.database"."no.such.schema"."no.such.function"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.function"
+\dF "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.database"."no.such.schema"."no.such.text.search.parser"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.database"."no.such.schema"."no.such.text.search.template"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.text.search.template"
+\do "no.such.database"."no.such.schema"."no.such.operator"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.operator"
+\dO "no.such.database"."no.such.schema"."no.such.collation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.collation"
+\dp "no.such.database"."no.such.schema"."no.such.access.privilege"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.access.privilege"
+\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+\dT "no.such.database"."no.such.schema"."no.such.data.type"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type"
+\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
+cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics"
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index bf372c37a5..6fc0ac6bd1 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1463,3 +1463,245 @@ SELECT * FROM bla ORDER BY 1;
\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
DROP TABLE bla;
DROP FUNCTION psql_error;
+
+-- check describing invalid multipart names
+\dA regression.heap
+\dA nonesuch.heap
+\dt host.regression.pg_catalog.pg_class
+\dt |.pg_catalog.pg_class
+\dt nonesuch.pg_catalog.pg_class
+\da host.regression.pg_catalog.sum
+\da +.pg_catalog.sum
+\da nonesuch.pg_catalog.sum
+\dAc nonesuch.brin
+\dAc regression.brin
+\dAf nonesuch.brin
+\dAf regression.brin
+\dAo nonesuch.brin
+\dAo regression.brin
+\dAp nonesuch.brin
+\dAp regression.brin
+\db nonesuch.pg_default
+\db regression.pg_default
+\dc host.regression.public.conversion
+\dc (.public.conversion
+\dc nonesuch.public.conversion
+\dC host.regression.pg_catalog.int8
+\dC ).pg_catalog.int8
+\dC nonesuch.pg_catalog.int8
+\dd host.regression.pg_catalog.pg_class
+\dd [.pg_catalog.pg_class
+\dd nonesuch.pg_catalog.pg_class
+\dD host.regression.public.gtestdomain1
+\dD ].public.gtestdomain1
+\dD nonesuch.public.gtestdomain1
+\ddp host.regression.pg_catalog.pg_class
+\ddp {.pg_catalog.pg_class
+\ddp nonesuch.pg_catalog.pg_class
+\dE host.regression.public.ft
+\dE }.public.ft
+\dE nonesuch.public.ft
+\di host.regression.public.tenk1_hundred
+\di ..public.tenk1_hundred
+\di nonesuch.public.tenk1_hundred
+\dm host.regression.public.mvtest_bb
+\dm ^.public.mvtest_bb
+\dm nonesuch.public.mvtest_bb
+\ds host.regression.public.check_seq
+\ds regression|mydb.public.check_seq
+\ds nonesuch.public.check_seq
+\dt host.regression.public.b_star
+\dt regres+ion.public.b_star
+\dt nonesuch.public.b_star
+\dv host.regression.public.shoe
+\dv regress(ion).public.shoe
+\dv nonesuch.public.shoe
+\des nonesuch.server
+\des regression.server
+\des nonesuch.server
+\des regression.server
+\des nonesuch.username
+\des regression.username
+\dew nonesuch.fdw
+\dew regression.fdw
+\df host.regression.public.namelen
+\df regres[qrstuv]ion.public.namelen
+\df nonesuch.public.namelen
+\dF host.regression.pg_catalog.arabic
+\dF regres{1,2}ion.pg_catalog.arabic
+\dF nonesuch.pg_catalog.arabic
+\dFd host.regression.pg_catalog.arabic_stem
+\dFd regres?ion.pg_catalog.arabic_stem
+\dFd nonesuch.pg_catalog.arabic_stem
+\dFp host.regression.pg_catalog.default
+\dFp ^regression.pg_catalog.default
+\dFp nonesuch.pg_catalog.default
+\dFt host.regression.pg_catalog.ispell
+\dFt regression$.pg_catalog.ispell
+\dFt nonesuch.pg_catalog.ispell
+\dg nonesuch.pg_database_owner
+\dg regression.pg_database_owner
+\dL host.regression.plpgsql
+\dL *.plpgsql
+\dL nonesuch.plpgsql
+\dn host.regression.public
+\dn """".public
+\dn nonesuch.public
+\do host.regression.public.!=-
+\do "regression|mydb".public.!=-
+\do nonesuch.public.!=-
+\dO host.regression.pg_catalog.POSIX
+\dO .pg_catalog.POSIX
+\dO nonesuch.pg_catalog.POSIX
+\dp host.regression.public.a_star
+\dp "regres+ion".public.a_star
+\dp nonesuch.public.a_star
+\dP host.regression.public.mlparted
+\dP "regres(sion)".public.mlparted
+\dP nonesuch.public.mlparted
+\drds nonesuch.lc_messages
+\drds regression.lc_messages
+\dRp public.mypub
+\dRp regression.mypub
+\dRs public.mysub
+\dRs regression.mysub
+\dT host.regression.public.widget
+\dT "regression{1,2}".public.widget
+\dT nonesuch.public.widget
+\dx regression.plpgsql
+\dx nonesuch.plpgsql
+\dX host.regression.public.func_deps_stat
+\dX "^regression$".public.func_deps_stat
+\dX nonesuch.public.func_deps_stat
+\dy regression.myevt
+\dy nonesuch.myevt
+
+-- check that dots within quoted name segments are not counted
+\dA "no.such.access.method"
+\dt "no.such.table.relation"
+\da "no.such.aggregate.function"
+\dAc "no.such.operator.class"
+\dAf "no.such.operator.family"
+\dAo "no.such.operator.of.operator.family"
+\dAp "no.such.operator.support.function.of.operator.family"
+\db "no.such.tablespace"
+\dc "no.such.conversion"
+\dC "no.such.cast"
+\dd "no.such.object.description"
+\dD "no.such.domain"
+\ddp "no.such.default.access.privilege"
+\di "no.such.index.relation"
+\dm "no.such.materialized.view"
+\ds "no.such.relation"
+\dt "no.such.relation"
+\dv "no.such.relation"
+\des "no.such.foreign.server"
+\dew "no.such.foreign.data.wrapper"
+\df "no.such.function"
+\dF "no.such.text.search.configuration"
+\dFd "no.such.text.search.dictionary"
+\dFp "no.such.text.search.parser"
+\dFt "no.such.text.search.template"
+\dg "no.such.role"
+\dL "no.such.language"
+\dn "no.such.schema"
+\do "no.such.operator"
+\dO "no.such.collation"
+\dp "no.such.access.privilege"
+\dP "no.such.partitioned.relation"
+\drds "no.such.setting"
+\dRp "no.such.publication"
+\dRs "no.such.subscription"
+\dT "no.such.data.type"
+\dx "no.such.installed.extension"
+\dX "no.such.extended.statistics"
+\dy "no.such.event.trigger"
+
+-- again, but with dotted schema qualifications.
+\dA "no.such.schema"."no.such.access.method"
+\dt "no.such.schema"."no.such.table.relation"
+\da "no.such.schema"."no.such.aggregate.function"
+\dAc "no.such.schema"."no.such.operator.class"
+\dAf "no.such.schema"."no.such.operator.family"
+\dAo "no.such.schema"."no.such.operator.of.operator.family"
+\dAp "no.such.schema"."no.such.operator.support.function.of.operator.family"
+\db "no.such.schema"."no.such.tablespace"
+\dc "no.such.schema"."no.such.conversion"
+\dC "no.such.schema"."no.such.cast"
+\dd "no.such.schema"."no.such.object.description"
+\dD "no.such.schema"."no.such.domain"
+\ddp "no.such.schema"."no.such.default.access.privilege"
+\di "no.such.schema"."no.such.index.relation"
+\dm "no.such.schema"."no.such.materialized.view"
+\ds "no.such.schema"."no.such.relation"
+\dt "no.such.schema"."no.such.relation"
+\dv "no.such.schema"."no.such.relation"
+\des "no.such.schema"."no.such.foreign.server"
+\dew "no.such.schema"."no.such.foreign.data.wrapper"
+\df "no.such.schema"."no.such.function"
+\dF "no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.schema"."no.such.text.search.template"
+\dg "no.such.schema"."no.such.role"
+\dL "no.such.schema"."no.such.language"
+\do "no.such.schema"."no.such.operator"
+\dO "no.such.schema"."no.such.collation"
+\dp "no.such.schema"."no.such.access.privilege"
+\dP "no.such.schema"."no.such.partitioned.relation"
+\drds "no.such.schema"."no.such.setting"
+\dRp "no.such.schema"."no.such.publication"
+\dRs "no.such.schema"."no.such.subscription"
+\dT "no.such.schema"."no.such.data.type"
+\dx "no.such.schema"."no.such.installed.extension"
+\dX "no.such.schema"."no.such.extended.statistics"
+\dy "no.such.schema"."no.such.event.trigger"
+
+-- again, but with current database and dotted schema qualifications.
+\dt regression."no.such.schema"."no.such.table.relation"
+\da regression."no.such.schema"."no.such.aggregate.function"
+\dc regression."no.such.schema"."no.such.conversion"
+\dC regression."no.such.schema"."no.such.cast"
+\dd regression."no.such.schema"."no.such.object.description"
+\dD regression."no.such.schema"."no.such.domain"
+\di regression."no.such.schema"."no.such.index.relation"
+\dm regression."no.such.schema"."no.such.materialized.view"
+\ds regression."no.such.schema"."no.such.relation"
+\dt regression."no.such.schema"."no.such.relation"
+\dv regression."no.such.schema"."no.such.relation"
+\df regression."no.such.schema"."no.such.function"
+\dF regression."no.such.schema"."no.such.text.search.configuration"
+\dFd regression."no.such.schema"."no.such.text.search.dictionary"
+\dFp regression."no.such.schema"."no.such.text.search.parser"
+\dFt regression."no.such.schema"."no.such.text.search.template"
+\do regression."no.such.schema"."no.such.operator"
+\dO regression."no.such.schema"."no.such.collation"
+\dp regression."no.such.schema"."no.such.access.privilege"
+\dP regression."no.such.schema"."no.such.partitioned.relation"
+\dT regression."no.such.schema"."no.such.data.type"
+\dX regression."no.such.schema"."no.such.extended.statistics"
+
+-- again, but with dotted database and dotted schema qualifications.
+\dt "no.such.database"."no.such.schema"."no.such.table.relation"
+\da "no.such.database"."no.such.schema"."no.such.aggregate.function"
+\dc "no.such.database"."no.such.schema"."no.such.conversion"
+\dC "no.such.database"."no.such.schema"."no.such.cast"
+\dd "no.such.database"."no.such.schema"."no.such.object.description"
+\dD "no.such.database"."no.such.schema"."no.such.domain"
+\ddp "no.such.database"."no.such.schema"."no.such.default.access.privilege"
+\di "no.such.database"."no.such.schema"."no.such.index.relation"
+\dm "no.such.database"."no.such.schema"."no.such.materialized.view"
+\ds "no.such.database"."no.such.schema"."no.such.relation"
+\dt "no.such.database"."no.such.schema"."no.such.relation"
+\dv "no.such.database"."no.such.schema"."no.such.relation"
+\df "no.such.database"."no.such.schema"."no.such.function"
+\dF "no.such.database"."no.such.schema"."no.such.text.search.configuration"
+\dFd "no.such.database"."no.such.schema"."no.such.text.search.dictionary"
+\dFp "no.such.database"."no.such.schema"."no.such.text.search.parser"
+\dFt "no.such.database"."no.such.schema"."no.such.text.search.template"
+\do "no.such.database"."no.such.schema"."no.such.operator"
+\dO "no.such.database"."no.such.schema"."no.such.collation"
+\dp "no.such.database"."no.such.schema"."no.such.access.privilege"
+\dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
+\dT "no.such.database"."no.such.schema"."no.such.data.type"
+\dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
--
2.35.1
On Tue, Apr 19, 2022 at 10:20 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
Looks like most people voted for (B). In support of that option, here are patches for master and REL_14_STABLE. Note that I extended the tests compared to v9, which found a problem that is fixed for v10:
OK, I committed these. I am not totally sure we've got all the
problems sorted here, but I don't think that continuing to not commit
anything is going to be better, so here we go.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Apr 21, 2022 at 3:55 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Apr 19, 2022 at 10:20 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:Looks like most people voted for (B). In support of that option, here are patches for master and REL_14_STABLE. Note that I extended the tests compared to v9, which found a problem that is fixed for v10:
OK, I committed these. I am not totally sure we've got all the
problems sorted here, but I don't think that continuing to not commit
anything is going to be better, so here we go.
Looks like this somehow broke on a Windows box:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jacana&dt=2022-04-20%2016%3A34%3A19
[14:05:49.729](0.001s) not ok 16 - pg_dumpall: option
--exclude-database rejects multipart pattern ".*": matches
[14:05:49.730](0.000s)
[14:05:49.730](0.000s) # Failed test 'pg_dumpall: option
--exclude-database rejects multipart pattern ".*": matches'
# at t/002_pg_dump.pl line 3985.
[14:05:49.730](0.000s) # 'pg_dumpall: error:
improper qualified name (too many dotted names): .gitignore
# '
# doesn't match '(?^:pg_dumpall: error: improper qualified name
\\(too many dotted names\\): \\.\\*)'
On Wed, Apr 20, 2022 at 3:08 PM Thomas Munro <thomas.munro@gmail.com> wrote:
Looks like this somehow broke on a Windows box:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jacana&dt=2022-04-20%2016%3A34%3A19
So the issue here is that we are running this command:
pg_dumpall --exclude-database .*
And on that Windows machine, .* is being expanded to .gitignore, so
pg_dumpall prints:
pg_dumpall: error: improper qualified name (too many dotted names): .gitignore
Instead of:
pg_dumpall: error: improper qualified name (too many dotted names): .*
I don't know why that glob-expansion only happens on jacana, and I
don't know how to fix it, either.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Apr 21, 2022 at 7:35 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Apr 20, 2022 at 3:08 PM Thomas Munro <thomas.munro@gmail.com> wrote:
Looks like this somehow broke on a Windows box:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jacana&dt=2022-04-20%2016%3A34%3A19
So the issue here is that we are running this command:
pg_dumpall --exclude-database .*
And on that Windows machine, .* is being expanded to .gitignore, so
pg_dumpall prints:pg_dumpall: error: improper qualified name (too many dotted names): .gitignore
Instead of:
pg_dumpall: error: improper qualified name (too many dotted names): .*
I don't know why that glob-expansion only happens on jacana, and I
don't know how to fix it, either.
Perhaps bowerbird and jacana have different versions of IPC::Run? I
see some recent-ish changes to escaping logic in here from Noah:
https://github.com/toddr/IPC-Run/commits/master/lib/IPC/Run/Win32Helper.pm
Looks like the older version looks for meta characters not including
'*', and the later one uses Win32::ShellQuote::quote_native.
On Thu, Apr 21, 2022 at 8:38 AM Thomas Munro <thomas.munro@gmail.com> wrote:
On Thu, Apr 21, 2022 at 7:35 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Apr 20, 2022 at 3:08 PM Thomas Munro <thomas.munro@gmail.com> wrote:
Looks like this somehow broke on a Windows box:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=jacana&dt=2022-04-20%2016%3A34%3A19
So the issue here is that we are running this command:
pg_dumpall --exclude-database .*
And on that Windows machine, .* is being expanded to .gitignore, so
pg_dumpall prints:pg_dumpall: error: improper qualified name (too many dotted names): .gitignore
Instead of:
pg_dumpall: error: improper qualified name (too many dotted names): .*
I don't know why that glob-expansion only happens on jacana, and I
don't know how to fix it, either.Perhaps bowerbird and jacana have different versions of IPC::Run? I
see some recent-ish changes to escaping logic in here from Noah:https://github.com/toddr/IPC-Run/commits/master/lib/IPC/Run/Win32Helper.pm
Looks like the older version looks for meta characters not including
'*', and the later one uses Win32::ShellQuote::quote_native.
This time with Andrew in CC.