> 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.
From f7024ae2e95bd28c47267d97d7350f43a2b6c072 Mon Sep 17 00:00:00 2001
From: Dian M Fay <dian.m....@gmail.com>
Date: Sun, 7 Mar 2021 02:59:14 -0500
Subject: [PATCH] Suppress explicit casts of text constants in postgres_fdw
 when the other side of a binary OpExpr is also text.

---
 contrib/postgres_fdw/deparse.c                | 27 ++++++-
 .../postgres_fdw/expected/postgres_fdw.out    | 81 ++++++++++++-------
 contrib/postgres_fdw/sql/postgres_fdw.sql     |  9 +++
 3 files changed, 84 insertions(+), 33 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 6faf499f9a..738c9c1038 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -100,6 +100,7 @@ typedef struct deparse_expr_cxt
                                                                 * a base 
relation. */
        StringInfo      buf;                    /* output buffer to append to */
        List      **params_list;        /* exprs that will become remote Params 
*/
+       int                     const_showtype; /* override showtype in 
deparseConst */
 } deparse_expr_cxt;
 
 #define REL_ALIAS_PREFIX       "r"
@@ -1018,6 +1019,7 @@ deparseSelectStmtForRel(StringInfo buf, PlannerInfo 
*root, RelOptInfo *rel,
        context.foreignrel = rel;
        context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel;
        context.params_list = params_list;
+       context.const_showtype = 0;
 
        /* Construct SELECT clause */
        deparseSelectSql(tlist, is_subquery, retrieved_attrs, &context);
@@ -1597,6 +1599,7 @@ deparseFromExprForRel(StringInfo buf, PlannerInfo *root, 
RelOptInfo *foreignrel,
                        context.scanrel = foreignrel;
                        context.root = root;
                        context.params_list = params_list;
+                       context.const_showtype = 0;
 
                        appendStringInfoChar(buf, '(');
                        appendConditions(fpinfo->joinclauses, &context);
@@ -1897,6 +1900,7 @@ deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
        context.scanrel = foreignrel;
        context.buf = buf;
        context.params_list = params_list;
+       context.const_showtype = 0;
 
        appendStringInfoString(buf, "UPDATE ");
        deparseRelation(buf, rel);
@@ -2005,6 +2009,7 @@ deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
        context.scanrel = foreignrel;
        context.buf = buf;
        context.params_list = params_list;
+       context.const_showtype = 0;
 
        appendStringInfoString(buf, "DELETE FROM ");
        deparseRelation(buf, rel);
@@ -2392,7 +2397,7 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
                        deparseVar((Var *) node, context);
                        break;
                case T_Const:
-                       deparseConst((Const *) node, context, 0);
+                       deparseConst((Const *) node, context, 
context->const_showtype);
                        break;
                case T_Param:
                        deparseParam((Param *) node, context);
@@ -2762,6 +2767,9 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context)
        StringInfo      buf = context->buf;
        HeapTuple       tuple;
        Form_pg_operator form;
+       Expr       *left;
+       Expr       *right;
+       Oid                     rightType;
        char            oprkind;
 
        /* Retrieve information about the operator from system catalog. */
@@ -2778,11 +2786,21 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context)
        /* Always parenthesize the expression. */
        appendStringInfoChar(buf, '(');
 
+       right = llast(node->args);
+
        /* Deparse left operand, if any. */
        if (oprkind == 'b')
        {
-               deparseExpr(linitial(node->args), context);
+               left = linitial(node->args);
+
+               deparseExpr(left, context);
                appendStringInfoChar(buf, ' ');
+               rightType = exprType((Node *) right);
+
+               if (rightType == TEXTOID && rightType == exprType((Node 
*)left)) {
+                       /* Suppress const casting for text values against text 
columns. */
+                       context->const_showtype = -1;
+               }
        }
 
        /* Deparse operator name. */
@@ -2790,11 +2808,14 @@ deparseOpExpr(OpExpr *node, deparse_expr_cxt *context)
 
        /* Deparse right operand. */
        appendStringInfoChar(buf, ' ');
-       deparseExpr(llast(node->args), context);
+       deparseExpr(right, context);
 
        appendStringInfoChar(buf, ')');
 
        ReleaseSysCache(tuple);
+
+       /* Release cast suppression in case it was set. */
+       context->const_showtype = 0;
 }
 
 /*
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out 
b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..78b7ca2c42 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -341,11 +341,11 @@ SELECT * FROM ft1 WHERE false;
 
 -- with WHERE clause
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 
= '1' AND t1.c7 >= '1';
-                                                                   QUERY PLAN  
                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                QUERY PLAN     
                                                           
+------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan on public.ft1 t1
    Output: c1, c2, c3, c4, c5, c6, c7, c8
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE 
((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'::text))
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE 
((c7 >= '1'::bpchar)) AND (("C 1" = 101)) AND ((c6 = '1'))
 (3 rows)
 
 SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
@@ -707,11 +707,11 @@ EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE 
c1 = (ARRAY[c1,c2,3])[1]
 (3 rows)
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c6 = E'foo''s\\bar';  
-- check special chars
-                                                 QUERY PLAN                    
                              
--------------------------------------------------------------------------------------------------------------
+                                              QUERY PLAN                       
                        
+-------------------------------------------------------------------------------------------------------
  Foreign Scan on public.ft1 t1
    Output: c1, c2, c3, c4, c5, c6, c7, c8
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE 
((c6 = E'foo''s\\bar'::text))
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE 
((c6 = E'foo''s\\bar'))
 (3 rows)
 
 EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 t1 WHERE c8 = 'foo';  -- can't 
be sent to remote
@@ -3396,12 +3396,12 @@ ORDER BY ref_0."C 1";
                Index Cond: (ref_0."C 1" < 10)
          ->  Foreign Scan on public.ft1 ref_1
                Output: ref_1.c3, ref_0.c2
-               Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = 
'00001'::text))
+               Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'))
    ->  Materialize
          Output: ref_3.c3
          ->  Foreign Scan on public.ft2 ref_3
                Output: ref_3.c3
-               Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = 
'00001'::text))
+               Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE ((c3 = '00001'))
 (15 rows)
 
 SELECT ref_0.c2, subq_1.*
@@ -4105,6 +4105,27 @@ ERROR:  invalid input syntax for type integer: "foo"
 CONTEXT:  processing expression at position 2 in select list
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 -- ===================================================================
+-- conversion for const matching local type
+-- ===================================================================
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1;
+ c1 | c2 |  c3   |              c4              |            c5            | 
c6 |     c7     | c8  
+----+----+-------+------------------------------+--------------------------+----+------------+-----
+  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 
 | 1          | foo
+(1 row)
+
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text;
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1;
+ c1 | c2 |  c3   |              c4              |            c5            | 
c6 |     c7     | c8  
+----+----+-------+------------------------------+--------------------------+----+------------+-----
+  1 |  1 | 00001 | Fri Jan 02 00:00:00 1970 PST | Fri Jan 02 00:00:00 1970 | 1 
 | 1          | foo
+(1 row)
+
+SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR
+ERROR:  operator does not exist: public.user_enum ~~ unknown
+HINT:  No operator matches the given name and argument types. You might need 
to add explicit type casts.
+CONTEXT:  remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 
1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT 1::bigint
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
+-- ===================================================================
 -- subtransaction
 --  + local/remote error doesn't break cursor
 -- ===================================================================
@@ -4154,35 +4175,35 @@ create foreign table ft3 (f1 text collate "C", f2 text, 
f3 varchar(10))
   server loopback options (table_name 'loct3', use_remote_estimate 'true');
 -- can be sent to remote
 explain (verbose, costs off) select * from ft3 where f1 = 'foo';
-                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
+                               QUERY PLAN                               
+------------------------------------------------------------------------
  Foreign Scan on public.ft3
    Output: f1, f2, f3
-   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text))
+   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'))
 (3 rows)
 
 explain (verbose, costs off) select * from ft3 where f1 COLLATE "C" = 'foo';
-                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
+                               QUERY PLAN                               
+------------------------------------------------------------------------
  Foreign Scan on public.ft3
    Output: f1, f2, f3
-   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'::text))
+   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f1 = 'foo'))
 (3 rows)
 
 explain (verbose, costs off) select * from ft3 where f2 = 'foo';
-                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
+                               QUERY PLAN                               
+------------------------------------------------------------------------
  Foreign Scan on public.ft3
    Output: f1, f2, f3
-   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'::text))
+   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f2 = 'foo'))
 (3 rows)
 
 explain (verbose, costs off) select * from ft3 where f3 = 'foo';
-                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
+                               QUERY PLAN                               
+------------------------------------------------------------------------
  Foreign Scan on public.ft3
    Output: f1, f2, f3
-   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'::text))
+   Remote SQL: SELECT f1, f2, f3 FROM public.loct3 WHERE ((f3 = 'foo'))
 (3 rows)
 
 explain (verbose, costs off) select * from ft3 f, loct3 l
@@ -4284,22 +4305,22 @@ INSERT INTO ft2 (c1,c2,c3)
 INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;         
     -- can be pushed down
-                                                      QUERY PLAN               
                                       
-----------------------------------------------------------------------------------------------------------------------
+                                                   QUERY PLAN                  
                                 
+----------------------------------------------------------------------------------------------------------------
  Update on public.ft2
    ->  Foreign Update on public.ft2
-         Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 300), c3 = (c3 || 
'_update3'::text) WHERE ((("C 1" % 10) = 3))
+         Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 300), c3 = (c3 || 
'_update3') WHERE ((("C 1" % 10) = 3))
 (3 rows)
 
 UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 
RETURNING *;  -- can be pushed down
-                                                                            
QUERY PLAN                                                                      
      
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                         QUERY 
PLAN                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2
    Output: c1, c2, c3, c4, c5, c6, c7, c8
    ->  Foreign Update on public.ft2
-         Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || 
'_update7'::text) WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, 
c6, c7, c8
+         Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || 
'_update7') WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, 
c8
 (4 rows)
 
 UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 
RETURNING *;
@@ -4412,11 +4433,11 @@ UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' 
WHERE c1 % 10 = 7 RETURNING
 EXPLAIN (verbose, costs off)
 UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
   FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;                          
     -- can be pushed down
-                                                                               
                    QUERY PLAN                                                  
                                                  
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+                                                                               
                 QUERY PLAN                                                     
                                            
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Update on public.ft2
    ->  Foreign Update
-         Remote SQL: UPDATE "S 1"."T 1" r1 SET c2 = (r1.c2 + 500), c3 = (r1.c3 
|| '_update9'::text), c7 = 'ft2       '::character(10) FROM "S 1"."T 1" r2 
WHERE ((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9))
+         Remote SQL: UPDATE "S 1"."T 1" r1 SET c2 = (r1.c2 + 500), c3 = (r1.c3 
|| '_update9'), c7 = 'ft2       '::character(10) FROM "S 1"."T 1" r2 WHERE 
((r1.c2 = r2."C 1")) AND (((r2."C 1" % 10) = 9))
 (3 rows)
 
 UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
@@ -7834,7 +7855,7 @@ select tableoid::regclass, * FROM locp;
 update utrtest set a = 2 where b = 'foo' returning *;
 ERROR:  new row for relation "loct" violates check constraint "loct_a_check"
 DETAIL:  Failing row contains (2, foo).
-CONTEXT:  remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 
'foo'::text)) RETURNING a, b
+CONTEXT:  remote SQL command: UPDATE public.loct SET a = 2 WHERE ((b = 'foo')) 
RETURNING a, b
 -- But the reverse is allowed
 update utrtest set a = 1 where b = 'qux' returning *;
  a |  b  
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql 
b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 2b525ea44a..f25941a022 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1133,6 +1133,15 @@ SELECT  ft1.c1,  ft2.c2, ft1 FROM ft1, ft2 WHERE ft1.c1 
= ft2.c1 AND ft1.c1 = 1;
 SELECT sum(c2), array_agg(c8) FROM ft1 GROUP BY c8; -- ERROR
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 
+-- ===================================================================
+-- conversion for const matching local type
+-- ===================================================================
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text;
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1;
+SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
+
 -- ===================================================================
 -- subtransaction
 --  + local/remote error doesn't break cursor
-- 
2.30.1

Reply via email to