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.
From 09961e10daf72e2c1fbbdddb05b2940b2da14df0 Mon Sep 17 00:00:00 2001
From: Dian M Fay <dian.m....@gmail.com>
Date: Mon, 1 Mar 2021 00:44:15 -0500
Subject: [PATCH] postgres_fdw: column option to override foreign types

Enabling the use_local_type option on a foreign table column will
deparse it with a cast to the locally-declared type, allowing overrides
of foreign types not known to the local database.
---
 contrib/postgres_fdw/deparse.c                 | 15 ++++++++++++---
 contrib/postgres_fdw/expected/postgres_fdw.out | 17 +++++++++++++++++
 contrib/postgres_fdw/option.c                  |  1 +
 contrib/postgres_fdw/sql/postgres_fdw.sql      | 10 ++++++++++
 doc/src/sgml/postgres-fdw.sgml                 | 13 +++++++++++++
 5 files changed, 53 insertions(+), 3 deletions(-)

diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 6faf499f9a..c722d49316 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -2264,6 +2264,7 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, 
RangeTblEntry *rte,
        else
        {
                char       *colname = NULL;
+               bool       uselocaltype = NULL;
                List       *options;
                ListCell   *lc;
 
@@ -2280,10 +2281,9 @@ deparseColumnRef(StringInfo buf, int varno, int 
varattno, RangeTblEntry *rte,
                        DefElem    *def = (DefElem *) lfirst(lc);
 
                        if (strcmp(def->defname, "column_name") == 0)
-                       {
                                colname = defGetString(def);
-                               break;
-                       }
+                       else if (strcmp(def->defname, "use_local_type") == 0)
+                               uselocaltype = defGetBoolean(def);
                }
 
                /*
@@ -2297,6 +2297,15 @@ deparseColumnRef(StringInfo buf, int varno, int 
varattno, RangeTblEntry *rte,
                        ADD_REL_QUALIFIER(buf, varno);
 
                appendStringInfoString(buf, quote_identifier(colname));
+
+               if (uselocaltype)
+               {
+                       Oid coltype = get_atttype(rte->relid, varattno);
+                       char *typname = deparse_type_name(coltype, -1);
+
+                       appendStringInfoString(buf, "::");
+                       appendStringInfoString(buf, typname);
+               }
        }
 }
 
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out 
b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0649b6b81c..c3271bac8f 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -4105,6 +4105,23 @@ 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 with use_local_type
+-- ===================================================================
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text;
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; -- ERROR
+ERROR:  operator does not exist: public.user_enum = text
+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'::text)) LIMIT 1::bigint
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (use_local_type 'true');
+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 OPTIONS (SET use_local_type 'false');
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
+-- ===================================================================
 -- subtransaction
 --  + local/remote error doesn't break cursor
 -- ===================================================================
diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c
index 64698c4da3..4d9ed3fc0f 100644
--- a/contrib/postgres_fdw/option.c
+++ b/contrib/postgres_fdw/option.c
@@ -200,6 +200,7 @@ InitPgFdwOptions(void)
                {"schema_name", ForeignTableRelationId, false},
                {"table_name", ForeignTableRelationId, false},
                {"column_name", AttributeRelationId, false},
+               {"use_local_type", AttributeRelationId, false},
                /* use_remote_estimate is available on both server and table */
                {"use_remote_estimate", ForeignServerRelationId, false},
                {"use_remote_estimate", ForeignTableRelationId, false},
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql 
b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 2b525ea44a..5db2e2b575 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -1133,6 +1133,16 @@ 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 with use_local_type
+-- ===================================================================
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE text;
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1; -- ERROR
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (use_local_type 'true');
+SELECT * FROM ft1 WHERE c8 = 'foo' LIMIT 1;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 OPTIONS (SET use_local_type 'false');
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
+
 -- ===================================================================
 -- subtransaction
 --  + local/remote error doesn't break cursor
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 07aa25799d..8486799e19 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -219,6 +219,19 @@ OPTIONS (ADD password_required 'false');
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>use_local_type</literal></term>
+     <listitem>
+      <para>
+       A foreign table column with this option set to <literal>true</literal>
+       will attempt to cast values and references to the type declared in
+       <command>CREATE FOREIGN TABLE</command>.  This allows, for example,
+       a text column to represent an enum column in the remote table without
+       maintaining the enum type in the local database as well.
+      </para>
+     </listitem>
+    </varlistentry>
+
    </variablelist>
 
   </sect3>
-- 
2.30.1

Reply via email to