[PATCH] postgres-fdw: column option to override foreign types

Started by Dian M Fayabout 5 years ago19 messageshackers
Jump to latest
#1Dian M Fay
dian.m.fay@gmail.com

Full use of a custom data type with postgres_fdw currently requires the
type be maintained in both the local and remote databases. `CREATE
FOREIGN TABLE` does not check declared types against the remote table,
but declaring e.g. a remote enum to be local text works only partway, as
seen here. A simple select query against alpha_items returns the enum
values as text; however, *filtering* on the column yields an error.

create database alpha;
create database beta;

\c alpha

create type itemtype as enum ('one', 'two', 'three');
create table items (
id serial not null primary key,
type itemtype not null
);
insert into items (type) values ('one'), ('one'), ('two');

\c beta

create extension postgres_fdw;
create server alpha foreign data wrapper postgres_fdw options (dbname 'alpha', host 'localhost', port '5432');
create user mapping for postgres server alpha options (user 'postgres');

create foreign table alpha_items (
id int,
type text
) server alpha options (table_name 'items');
select * from alpha_items; -- ok
select * from alpha_items where type = 'one';

ERROR: operator does not exist: public.itemtype = text
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
CONTEXT: remote SQL command: SELECT id, type FROM public.items WHERE ((type = 'one'::text))

The attached changeset adds a new boolean option for postgres_fdw
foreign table columns, `use_local_type`. When true, ColumnRefs for the
relevant attribute will be deparsed with a cast to the type defined in
`CREATE FOREIGN TABLE`.

create foreign table alpha_items (
id int,
type text options (use_local_type 'true')
) server alpha options (table_name 'items');
select * from alpha_items where type = 'one'; -- succeeds

This builds and checks, with a new regression test and documentation.

Attachments:

0001-postgres_fdw-column-option-to-override-foreign-types.patchtext/plain; charset=utf-8; name=0001-postgres_fdw-column-option-to-override-foreign-types.patchDownload+53-4
#2Ashutosh Bapat
ashutosh.bapat@enterprisedb.com
In reply to: Dian M Fay (#1)
Re: [PATCH] postgres-fdw: column option to override foreign types

On Mon, Mar 1, 2021 at 12:59 PM Dian M Fay <dian.m.fay@gmail.com> wrote:

Full use of a custom data type with postgres_fdw currently requires the
type be maintained in both the local and remote databases. `CREATE
FOREIGN TABLE` does not check declared types against the remote table,
but declaring e.g. a remote enum to be local text works only partway, as
seen here. A simple select query against alpha_items returns the enum
values as text; however, *filtering* on the column yields an error.

create database alpha;
create database beta;

\c alpha

create type itemtype as enum ('one', 'two', 'three');
create table items (
id serial not null primary key,
type itemtype not null
);
insert into items (type) values ('one'), ('one'), ('two');

\c beta

create extension postgres_fdw;
create server alpha foreign data wrapper postgres_fdw options (dbname 'alpha', host 'localhost', port '5432');
create user mapping for postgres server alpha options (user 'postgres');

create foreign table alpha_items (
id int,
type text
) server alpha options (table_name 'items');

postgres_fdw assumes that the local type declared is semantically same
as the remote type. Ideally the enum should also be declared locally
and used to declare type's datatype. See how to handle UDTs in
postgres_fdw at
https://stackoverflow.com/questions/37734170/can-the-foreign-data-wrapper-fdw-postgres-handle-the-geometry-data-type-of-postg

--
Best Wishes,
Ashutosh Bapat

#3Dian M Fay
dian.m.fay@gmail.com
In reply to: Ashutosh Bapat (#2)
Re: [PATCH] postgres-fdw: column option to override foreign types

On Tue Mar 2, 2021 at 6:50 AM EST, Ashutosh Bapat wrote:

On Mon, Mar 1, 2021 at 12:59 PM Dian M Fay <dian.m.fay@gmail.com> wrote:

Full use of a custom data type with postgres_fdw currently requires the
type be maintained in both the local and remote databases. `CREATE
FOREIGN TABLE` does not check declared types against the remote table,
but declaring e.g. a remote enum to be local text works only partway, as
seen here. A simple select query against alpha_items returns the enum
values as text; however, *filtering* on the column yields an error.

postgres_fdw assumes that the local type declared is semantically same
as the remote type. Ideally the enum should also be declared locally
and used to declare type's datatype. See how to handle UDTs in
postgres_fdw at
https://stackoverflow.com/questions/37734170/can-the-foreign-data-wrapper-fdw-postgres-handle-the-geometry-data-type-of-postg

I'm aware, and the reason for this change is that I think it's annoying
to declare and maintain the type on the local server for the sole
purpose of accommodating a read-only foreign table that effectively
treats it like text anyway. The real scenario that prompted it is a
tickets table with status, priority, category, etc. enums. We don't have
plans to modify them any time soon, but if we do it's got to be
coordinated and deployed across two databases, all so we can use what
might as well be a text column in a single WHERE clause. Since foreign
tables can be defined over subsets of columns, reordered, and names
changed, a little opt-in flexibility with types too doesn't seem
misplaced.

Note that currently, postgres_fdw will strip casts on the WHERE column:
`where type::text = 'one'` becomes `where ((type = 'one'::text))` (the
value is cast separately). Making it respect those is another option,
but I thought including it in column configuration would be less
surprising to users who aren't aware of the difference between the local
and remote tables.

#4Georgios Kokolatos
gkokolatos@protonmail.com
In reply to: Dian M Fay (#3)
Re: [PATCH] postgres-fdw: column option to override foreign types

The following review has been posted through the commitfest application:
make installcheck-world: not tested
Implements feature: not tested
Spec compliant: not tested
Documentation: not tested

Hi,

Thanks for the patch.

I am afraid I will have to :-1: this patch. Of course it is possible that I am wrong,
so please correct me if you, or any other reviewers, think so.

The problem that is intended to be solved, upon closer inspection seems to be a
conscious design decision rather than a problem. Even if I am wrong there, I am
not certain that the proposed patch covers all the bases with respect to collations,
build-in types, shipability etc for simple expressions, and covers any of more
complicated expressions all together.

As for the scenario which prompted the patch, you wrote, quote:

The real scenario that prompted it is a
tickets table with status, priority, category, etc. enums. We don't have
plans to modify them any time soon, but if we do it's got to be
coordinated and deployed across two databases, all so we can use what
might as well be a text column in a single WHERE clause. Since foreign
tables can be defined over subsets of columns, reordered, and names
changed, a little opt-in flexibility with types too doesn't seem
misplaced.

end quote.

I will add that creating a view on the remote server with type flexibility that
you wish and then create foreign tables against the view, might address your
problem.

Respectfully,
//Georgios

#5David Steele
david@pgmasters.net
In reply to: Georgios Kokolatos (#4)
Re: [PATCH] postgres-fdw: column option to override foreign types

On 3/4/21 9:28 AM, Georgios Kokolatos wrote:

I am afraid I will have to :-1: this patch. Of course it is possible that I am wrong,
so please correct me if you, or any other reviewers, think so.

I'm inclined to agree and it seems like a view on the source server is a
good compromise and eliminates the maintenance concerns.

I'm going to mark this as Waiting on Author for now, but will close it
on March 11 if there are no arguments in support.

Dian, perhaps you have another angle you'd like to try?

Regards,
--
-David
david@pgmasters.net

#6Dian M Fay
dian.m.fay@gmail.com
In reply to: David Steele (#5)
Re: [PATCH] postgres-fdw: column option to override foreign types

On Thu Mar 4, 2021 at 9:28 AM EST, Georgios Kokolatos wrote:

I am afraid I will have to :-1: this patch. Of course it is possible
that I am wrong,
so please correct me if you, or any other reviewers, think so.

The problem that is intended to be solved, upon closer inspection
seems
to be a
conscious design decision rather than a problem. Even if I am wrong
there, I am
not certain that the proposed patch covers all the bases with respect
to
collations,
build-in types, shipability etc for simple expressions, and covers any
of more
complicated expressions all together.

Thanks for reviewing it!

I see room for interpretation in the design here, although I have
admittedly not been looking at it for very long. `CREATE/ALTER FOREIGN
TABLE` take the user at their word about types, which only map 1:1 for a
foreign Postgres server anyway. In make_tuple_from_result_row, incoming
values start as strings until they're converted to their target types --
again, with no guarantee that those types match those on the remote
server. The docs recommend types match exactly and note the sorts of
things that can go wrong, but there's no enforcement; either what you've
cooked up works or it doesn't. And in fact, declaring local text for a
remote enum seems to work quite well.... right up until you try to
reference it in the `WHERE` clause.

Enum::text seems like a safe and potentially standardizable case for
postgres_fdw. As implemented, the patch goes beyond that, but it's
opt-in and the docs already warn about consequences. I haven't tested it
across collations, but right now that seems like something to look into
if the idea survives the next few messages.

I will add that creating a view on the remote server with type
flexibility that
you wish and then create foreign tables against the view, might
address
your
problem.

A view would address the immediate issue of the types, but itself
requires additional maintenance if/when the underlying table's schema
changes (even `SELECT *` is expanded into the current column definitions
at creation). I think it's better than copying the types, because it
moves the extra work of keeping local and remote synchronized to a
*table* modification as opposed to a *type* modification, in which
latter case it's much easier to forget about dependents. But I'd prefer
to avoid extra work anywhere!

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dian M Fay (#6)
Re: [PATCH] postgres-fdw: column option to override foreign types

"Dian M Fay" <dian.m.fay@gmail.com> writes:

On Thu Mar 4, 2021 at 9:28 AM EST, Georgios Kokolatos wrote:

I am afraid I will have to :-1: this patch.

I see room for interpretation in the design here, although I have
admittedly not been looking at it for very long. `CREATE/ALTER FOREIGN
TABLE` take the user at their word about types, which only map 1:1 for a
foreign Postgres server anyway.

Right.

In make_tuple_from_result_row, incoming
values start as strings until they're converted to their target types --
again, with no guarantee that those types match those on the remote
server.

The data conversion itself provides a little bit of security --- for
instance, converting 'foobar' to int or timestamp will fail. It's
not bulletproof, but on the other hand there are indeed situations
where you don't want to declare the column locally with exactly the
type the remote server is using, so trying to be bulletproof would
be counterproductive.

I am not, however, any more impressed than the other respondents with
the solution you've proposed. For one thing, this can only help if
the local type is known to the remote server, which seems to eliminate
fifty per cent of the use-cases for intentional differences in type.
(That is, isn't it equally as plausible that the local type is an
enum you didn't bother making on the remote side?) But a bigger issue
is that shipping
WHERE foreigncol::text = 'one'::text
to the remote server is not a nice solution even if it works. It will,
for example, defeat use of a normal index on foreigncol. It'd likely
be just as inefficient for remote joins.

What'd be better, if we could do it, is to ship the clause in
the form
WHERE foreigncol = 'one'
that is, instead of plastering a cast on the Var, try to not put
any explicit cast on the constant. That fixes your original use
case better than what you've proposed, and I think it might be
possible to do it unconditionally instead of needing a hacky
column property to enable it. The reason this could be okay
is that it seems reasonable for postgres_fdw to rely on the
core parser's heuristic that an unknown-type literal is the
same type as what it's being compared to. So, if we are trying
to deparse something of the form "foreigncol operator constant",
and the foreigncol and constant are of the same type locally,
we could leave off the cast on the constant. (There might need
to be some restrictions about the operator taking those types
natively with no cast, not sure; also this doesn't apply to
constants that are going to be printed as non-string literals.)

Slipping this heuristic into the code structure of deparse.c
might be rather messy, though. I've not looked at just how
to implement it.

regards, tom lane

#8Dian M Fay
dian.m.fay@gmail.com
In reply to: Tom Lane (#7)
Re: [PATCH] postgres-fdw: column option to override foreign types

What'd be better, if we could do it, is to ship the clause in
the form
WHERE foreigncol = 'one'
that is, instead of plastering a cast on the Var, try to not put
any explicit cast on the constant. That fixes your original use
case better than what you've proposed, and I think it might be
possible to do it unconditionally instead of needing a hacky
column property to enable it. The reason this could be okay
is that it seems reasonable for postgres_fdw to rely on the
core parser's heuristic that an unknown-type literal is the
same type as what it's being compared to. So, if we are trying
to deparse something of the form "foreigncol operator constant",
and the foreigncol and constant are of the same type locally,
we could leave off the cast on the constant. (There might need
to be some restrictions about the operator taking those types
natively with no cast, not sure; also this doesn't apply to
constants that are going to be printed as non-string literals.)

Slipping this heuristic into the code structure of deparse.c
might be rather messy, though. I've not looked at just how
to implement it.

This doesn't look too bad from here, at least so far. The attached
change adds a new const_showtype field to the deparse_expr_cxt, and
passes that instead of the hardcoded 0 to deparseConst. deparseOpExpr
modifies const_showtype if both sides of a binary operation are text,
and resets it to 0 after the recursion.

I restricted it to text-only after seeing a regression test fail: while
deparsing `percentile_cont(c2/10::numeric)`, c2, an integer column, is a
FuncExpr with a numeric return type. That matches the numeric 10, and
without the explicit cast, integer-division-related havoc ensues. I
don't know why it's a FuncExpr, and I don't know why it's not an int,
but the constant is definitely a non-string, in any case.

In the course of testing, I discovered that the @@ text-search operator
works against textified enums on my stock 13.1 server (a "proper" enum
column yields "operator does not exist"). I'm rather wary of actually
trying to depend on that behavior, although it seems probably-safe in
the same character set and collation.

Attachments:

0001-Suppress-explicit-casts-of-text-constants-in-postgre.patchtext/plain; charset=utf-8; name=0001-Suppress-explicit-casts-of-text-constants-in-postgre.patchDownload+84-25
#9Dian M Fay
dian.m.fay@gmail.com
In reply to: Dian M Fay (#8)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

On Sun Mar 7, 2021 at 2:37 AM EST, Dian M Fay wrote:

What'd be better, if we could do it, is to ship the clause in
the form
WHERE foreigncol = 'one'
that is, instead of plastering a cast on the Var, try to not put
any explicit cast on the constant. That fixes your original use
case better than what you've proposed, and I think it might be
possible to do it unconditionally instead of needing a hacky
column property to enable it. The reason this could be okay
is that it seems reasonable for postgres_fdw to rely on the
core parser's heuristic that an unknown-type literal is the
same type as what it's being compared to. So, if we are trying
to deparse something of the form "foreigncol operator constant",
and the foreigncol and constant are of the same type locally,
we could leave off the cast on the constant. (There might need
to be some restrictions about the operator taking those types
natively with no cast, not sure; also this doesn't apply to
constants that are going to be printed as non-string literals.)

Slipping this heuristic into the code structure of deparse.c
might be rather messy, though. I've not looked at just how
to implement it.

This doesn't look too bad from here, at least so far. The attached
change adds a new const_showtype field to the deparse_expr_cxt, and
passes that instead of the hardcoded 0 to deparseConst. deparseOpExpr
modifies const_showtype if both sides of a binary operation are text,
and resets it to 0 after the recursion.

I restricted it to text-only after seeing a regression test fail: while
deparsing `percentile_cont(c2/10::numeric)`, c2, an integer column, is a
FuncExpr with a numeric return type. That matches the numeric 10, and
without the explicit cast, integer-division-related havoc ensues. I
don't know why it's a FuncExpr, and I don't know why it's not an int,
but the constant is definitely a non-string, in any case.

In the course of testing, I discovered that the @@ text-search operator
works against textified enums on my stock 13.1 server (a "proper" enum
column yields "operator does not exist"). I'm rather wary of actually
trying to depend on that behavior, although it seems probably-safe in
the same character set and collation.

hello again! My second version of this change (suppressing the cast
entirely as Tom suggested) seemed to slip under the radar back in March
and then other matters intervened. I'm still interested in making it
happen, though, and now that we're out of another commitfest it seems
like a good time to bring it back up. Here's a rebased patch to start.

Attachments:

0001-Suppress-explicit-casts-of-text-constants-in-postgre.patchtext/plain; charset=utf-8; name=0001-Suppress-explicit-casts-of-text-constants-in-postgre.patchDownload+90-29
#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dian M Fay (#9)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

"Dian M Fay" <dian.m.fay@gmail.com> writes:

[ 0001-Suppress-explicit-casts-of-text-constants-in-postgre.patch ]

I took a quick look at this. The restriction to type text seems like
very obviously a hack rather than something we actually want; wouldn't
it mean we fail to act in a large fraction of the cases where we'd
like to suppress the cast?

A second problem is that I don't think the business with a const_showtype
context field is safe at all. As you've implemented it here, it would
affect the entire RHS tree, including constants far down inside complex
expressions that have nothing to do with the top-level semantics.
(I didn't look closely, but I wonder if the regression failure you
mentioned is associated with that.)

I think that we only want to suppress the cast in cases where
(1) the constant is directly an operand of the operator we're
expecting the remote parser to use its same-type heuristic for, and
(2) the constant will be deparsed as a string literal. (If it's
deparsed as a number, boolean, etc, then it won't be initially
UNKNOWN, so that heuristic won't be applied.)

Now point 1 means that we don't really need to mess with keeping
state in the recursion context. If we've determined at the level
of the OpExpr that we can do this, including checking that the
RHS operand IsA(Const), then we can just invoke deparseConst() on
it directly instead of recursing via deparseExpr().

Meanwhile, I suspect that point 2 might be best checked within
deparseConst() itself, as that contains both the decision and the
mechanism about how the Const will be printed. So that suggests
that we should invent a new showtype code telling deparseConst()
to act this way, and then supply that code directly when we
invoke deparseConst directly from deparseOpExpr.

BTW, don't we also want to be able to optimize cases where the Const
is on the LHS rather than the RHS?

regards, tom lane

#11Dian M Fay
dian.m.fay@gmail.com
In reply to: Tom Lane (#10)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

On Sun Sep 5, 2021 at 6:43 PM EDT, Tom Lane wrote:

"Dian M Fay" <dian.m.fay@gmail.com> writes:

[ 0001-Suppress-explicit-casts-of-text-constants-in-postgre.patch ]

I took a quick look at this. The restriction to type text seems like
very obviously a hack rather than something we actually want; wouldn't
it mean we fail to act in a large fraction of the cases where we'd
like to suppress the cast?

A second problem is that I don't think the business with a
const_showtype
context field is safe at all. As you've implemented it here, it would
affect the entire RHS tree, including constants far down inside complex
expressions that have nothing to do with the top-level semantics.
(I didn't look closely, but I wonder if the regression failure you
mentioned is associated with that.)

I think that we only want to suppress the cast in cases where
(1) the constant is directly an operand of the operator we're
expecting the remote parser to use its same-type heuristic for, and
(2) the constant will be deparsed as a string literal. (If it's
deparsed as a number, boolean, etc, then it won't be initially
UNKNOWN, so that heuristic won't be applied.)

Now point 1 means that we don't really need to mess with keeping
state in the recursion context. If we've determined at the level
of the OpExpr that we can do this, including checking that the
RHS operand IsA(Const), then we can just invoke deparseConst() on
it directly instead of recursing via deparseExpr().

Meanwhile, I suspect that point 2 might be best checked within
deparseConst() itself, as that contains both the decision and the
mechanism about how the Const will be printed. So that suggests
that we should invent a new showtype code telling deparseConst()
to act this way, and then supply that code directly when we
invoke deparseConst directly from deparseOpExpr.

BTW, don't we also want to be able to optimize cases where the Const
is on the LHS rather than the RHS?

regards, tom lane

Thanks Tom, that makes way more sense! I've attached a new patch which
tests operands and makes sure one side is a Const before feeding it to
deparseConst with a new showtype code, -2. The one regression is gone,
but I've left a couple of test output discrepancies for now which
showcase lost casts on the following predicates:

* date(c5) = '1970-01-17'::date
* ctid = '(0,2)'::tid

These aren't exactly failures -- both implicit string comparisons work
just fine -- but I don't know Postgres well enough to be sure that
that's true more generally. I did try checking that the non-Const member
of the predicate is a Var; that left the date cast alone, since date(c5)
is a FuncExpr, but obviously can't do anything about the tid.

There's also an interesting case where `val::text LIKE 'foo'` works when
val is an enum column in the local table, and breaks, castless, with an
operator mismatch when it's altered to text: Postgres' statement parser
recognizes the cast as redundant and creates a Var node instead of a
RelabelType (as it will for, say, `val::varchar(10)`) before the FDW is
even in the picture. It's a little discomfiting, but I suppose a certain
level of "caveat emptor" entails when disregarding foreign types.

(val as enum on local and remote)
explain verbose select * from test where (val::text) like 'foo';

Foreign Scan on public.test (cost=100.00..169.06 rows=8 width=28)
Output: id, val, on_day, ts, ts2
Filter: ((test.val)::text ~~ 'foo'::text)
Remote SQL: SELECT id, val, on_day, ts, ts2 FROM public.test

(val as local text, remote enum)
explain verbose select * from test where (val::text) like 'foo';

Foreign Scan on public.test (cost=100.00..122.90 rows=5 width=56)
Output: id, val, on_day, ts, ts2
Remote SQL: SELECT id, val, on_day, ts, ts2 FROM public.test WHERE ((val ~~ 'foo'))

explain verbose select * from test where (val::varchar(10)) like 'foo';

Foreign Scan on public.test (cost=100.00..125.46 rows=5 width=56)
Output: id, val, on_day, ts, ts2
Remote SQL: SELECT id, val, on_day, ts, ts2 FROM public.test WHERE ((val::character varying(10) ~~ 'foo'))

Outside that, deparseConst also contains a note about keeping the code
in sync with the parser (make_const in particular); from what I could
tell, I don't think there's anything in this that necessitates changes
there.

Attachments:

0001-Suppress-explicit-casts-of-safe-const-operands.patchtext/plain; charset=utf-8; name=0001-Suppress-explicit-casts-of-safe-const-operands.patchDownload+146-30
#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dian M Fay (#11)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

"Dian M Fay" <dian.m.fay@gmail.com> writes:

Thanks Tom, that makes way more sense! I've attached a new patch which
tests operands and makes sure one side is a Const before feeding it to
deparseConst with a new showtype code, -2. The one regression is gone,
but I've left a couple of test output discrepancies for now which
showcase lost casts on the following predicates:

* date(c5) = '1970-01-17'::date
* ctid = '(0,2)'::tid

These aren't exactly failures -- both implicit string comparisons work
just fine -- but I don't know Postgres well enough to be sure that
that's true more generally.

These seem fine to me. The parser heuristic that we're relying on
acts at the level of the operator --- it doesn't really care whether
the other input argument is a simple Var or not.

Note that we're *not* doing an "implicit string comparison" in either
case. The point here is that the remote parser will resolve the
unknown-type literal as being the same type as the other operator input,
that is date or tid in these two cases.

That being the goal, I think you don't have the logic right at all,
even if it happens to accidentally work in the tested cases. We
can only drop the cast if it's a binary operator and the two inputs
are of the same type. Testing "leftType == form->oprleft" is pretty
close to a no-op, because the input will have been coerced to the
operator's expected type. And the code as you had it could do
indefensible things with a unary operator. (It's probably hard to
get here with a unary operator applied to a constant, but I'm not
sure it's impossible.)

Attached is a rewrite that does what I think we want to do, and
also adds comments because there weren't any.

Now that I've looked this over I'm starting to feel uncomfortable
again, because we can't actually be quite sure about how the remote
parser's heuristic will act. What we're checking is that leftType
and rightType match, but that condition is applied to the inputs
*after implicit type coercion to the operator's input types*.
We can't be entirely sure about what our parser saw to begin with.
Perhaps it'd be a good idea to strip any implicit coercions on
the non-Const input before checking its type. I'm not sure how
much that helps though. For one thing, by the time this code
sees the expression, eval_const_expressions could have collapsed
coercion steps in a way that obscures how it looked originally.
For another thing, in the cases we're interested in, it's kind of
a stretch to suppose that implicit coercions applied locally are
a good model of the way things will look to the remote parser.

So I'm feeling a bit itchy. I'm still willing to push forward
with this, but I won't be terribly surprised if it breaks cases
that ought to work and we end up having to revert it.

regards, tom lane

Attachments:

0001-Suppress-explicit-casts-v5.patchtext/x-diff; charset=us-ascii; name=0001-Suppress-explicit-casts-v5.patchDownload+170-37
#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#12)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

I wrote:

Now that I've looked this over I'm starting to feel uncomfortable
again, because we can't actually be quite sure about how the remote
parser's heuristic will act.

Actually ... we could make that a lot safer by insisting that the
other input be a plain Var, which'd necessarily be a column of the
foreign table. That would still cover most cases of practical
interest, I think, and it would remove any question of whether
implicit coercions had snuck in. It's more restrictive than I'd
really like, but I think it's less likely to cause problems.

regards, tom lane

#14Dian M Fay
dian.m.fay@gmail.com
In reply to: Tom Lane (#13)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

On Tue Nov 2, 2021 at 7:10 PM EDT, Tom Lane wrote:

I wrote:

Now that I've looked this over I'm starting to feel uncomfortable
again, because we can't actually be quite sure about how the remote
parser's heuristic will act.

Actually ... we could make that a lot safer by insisting that the
other input be a plain Var, which'd necessarily be a column of the
foreign table. That would still cover most cases of practical
interest, I think, and it would remove any question of whether
implicit coercions had snuck in. It's more restrictive than I'd
really like, but I think it's less likely to cause problems.

Here's v6! I started with restricting cast suppression to Const-Var
comparisons as you suggested. A few tests did regress (relative to the
unrestricted version) right out of the gate with comparisons to varchar
columns, since those become RelabelType nodes instead of Vars. After
reading the notes on RelabelType in primnodes.h, I *think* that that
"dummy" coercion is distinct from the operator input type coercion
you're talking about here:

What we're checking is that leftType and rightType match, but that
condition is applied to the inputs *after implicit type coercion to
the operator's input types*. We can't be entirely sure about what our
parser saw to begin with. Perhaps it'd be a good idea to strip any
implicit coercions on the non-Const input before checking its type.

I allowed RelabelTypes over Vars to suppress casts as well. It's working
for me so far and the varchar comparison tests are back to passing, sans
casts.

Attachments:

v6-0001-Suppress-explicit-casts-of-safe-Consts-in-postgre.patchtext/plain; charset=utf-8; name=v6-0001-Suppress-explicit-casts-of-safe-Consts-in-postgre.patchDownload+177-36
#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dian M Fay (#14)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

"Dian M Fay" <dian.m.fay@gmail.com> writes:

On Tue Nov 2, 2021 at 7:10 PM EDT, Tom Lane wrote:

Actually ... we could make that a lot safer by insisting that the
other input be a plain Var, which'd necessarily be a column of the
foreign table. That would still cover most cases of practical
interest, I think, and it would remove any question of whether
implicit coercions had snuck in. It's more restrictive than I'd
really like, but I think it's less likely to cause problems.

I allowed RelabelTypes over Vars to suppress casts as well. It's working
for me so far and the varchar comparison tests are back to passing, sans
casts.

Um. I doubt that that's any safer than the v5 patch. As an example,
casting between int4 and oid is just a RelabelType, but the comparison
semantics change completely (signed vs. unsigned); so there's not a
good reason to think this is constraining things more than v5 did.

It might be better if you'd further restricted the structure to be only
COERCE_IMPLICIT_CAST RelabelTypes, since we don't normally make casts
implicit if they significantly change semantics. Also, this'd ensure
that the operand printed for the remote server is just a bare Var
(cf. deparseRelabelType). But even with that I'm feeling antsy about
whether this will allow any semantic surprises.

regards, tom lane

#16Dian M Fay
dian.m.fay@gmail.com
In reply to: Tom Lane (#15)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

On Mon Nov 8, 2021 at 4:50 PM EST, Tom Lane wrote:

Um. I doubt that that's any safer than the v5 patch. As an example,
casting between int4 and oid is just a RelabelType, but the comparison
semantics change completely (signed vs. unsigned); so there's not a
good reason to think this is constraining things more than v5 did.

It might be better if you'd further restricted the structure to be only
COERCE_IMPLICIT_CAST RelabelTypes, since we don't normally make casts
implicit if they significantly change semantics. Also, this'd ensure
that the operand printed for the remote server is just a bare Var
(cf. deparseRelabelType). But even with that I'm feeling antsy about
whether this will allow any semantic surprises.

I've split the suppression for RelabelTypes with implicit cast check
into a second patch over the core v7 change. As far as testing goes, \dC
lists implicit casts, but most of those I've tried seem to wind up
deparsing as Vars. I've been able to manifest RelabelTypes with varchar,
cidr, and remote char to local varchar, but that's about it. Any ideas
for validating it further, off the top of your head?

Attachments:

v7-0001-Suppress-explicit-casts-of-safe-Consts-in-postgre.patchtext/plain; charset=utf-8; name=v7-0001-Suppress-explicit-casts-of-safe-Consts-in-postgre.patchDownload+165-32
v7-0002-Extend-Const-cast-suppression-to-RelabelType-node.patchtext/plain; charset=utf-8; name=v7-0002-Extend-Const-cast-suppression-to-RelabelType-node.patchDownload+19-9
#17Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dian M Fay (#16)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

"Dian M Fay" <dian.m.fay@gmail.com> writes:

I've split the suppression for RelabelTypes with implicit cast check
into a second patch over the core v7 change. As far as testing goes, \dC
lists implicit casts, but most of those I've tried seem to wind up
deparsing as Vars. I've been able to manifest RelabelTypes with varchar,
cidr, and remote char to local varchar, but that's about it. Any ideas
for validating it further, off the top of your head?

I thought about this some more and realized exactly why I wanted to
restrict the change to cases where the other side is a plain foreign Var:
that way, if anything surprising happens, we can blame it directly on the
user having declared a local column with a different type from the
remote column.

That being the case, I took a closer look at deparseVar and realized that
we can't simply check "IsA(node, Var)": some Vars in the expression can
belong to local tables. We need to verify that the Var is one that will
print as a remote column reference.

So that leads me to v8, attached. I think we are getting there.

regards, tom lane

Attachments:

v8-0001-Suppress-explicit-casts.patchtext/x-diff; charset=us-ascii; name=v8-0001-Suppress-explicit-casts.patchDownload+212-35
#18Dian M Fay
dian.m.fay@gmail.com
In reply to: Tom Lane (#17)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

On Thu Nov 11, 2021 at 3:36 PM EST, Tom Lane wrote:

I thought about this some more and realized exactly why I wanted to
restrict the change to cases where the other side is a plain foreign
Var: that way, if anything surprising happens, we can blame it
directly on the user having declared a local column with a different
type from the remote column.

That being the case, I took a closer look at deparseVar and realized
that we can't simply check "IsA(node, Var)": some Vars in the
expression can belong to local tables. We need to verify that the Var
is one that will print as a remote column reference.

Eminently reasonable all around! `git apply` insisted that the v8 patch
didn't (apply, that is), but `patch -p1` liked it fine. I've put it
through a few paces and it seems good; what needs to happen next?

#19Tom Lane
tgl@sss.pgh.pa.us
In reply to: Dian M Fay (#18)
Re: [PATCH] postgres_fdw: suppress explicit casts in text:text comparisons (was: column option to override foreign types)

"Dian M Fay" <dian.m.fay@gmail.com> writes:

Eminently reasonable all around! `git apply` insisted that the v8 patch
didn't (apply, that is), but `patch -p1` liked it fine. I've put it
through a few paces and it seems good; what needs to happen next?

I don't see anything else to do except shove it out into the light
of day and see what happens. Hence, pushed.

As I remarked in the commit message:

One point that I (tgl) remain slightly uncomfortable with is that we
will ignore an implicit RelabelType when deciding if the non-Const input
is a plain Var. That makes it a little squishy to argue that the remote
should resolve the Const as being of the same type as its Var, because
then our Const is not the same type as our Var. However, if we don't do
that, then this hack won't work as desired if the user chooses to use
varchar rather than text to represent some remote column. That seems
useful, so do it like this for now. We might have to give up the
RelabelType-ignoring bit if any problems surface.

I think we can await complaints before doing more, but I wanted that
bit on record for anyone perusing the archives.

regards, tom lane