In PostgreSQL 9.3 Postgres_FDW: A Test Drive
we talked about taking Postgres Foreign Data Wrapper for a test drive. One downside of the Postgres FDW and actually most PostgreSQL FDWs is that
the foreign table structure can't be inspected from the source and the fields have to be explicitly stated in the foreign table definition. If you have a lot of tables to script, this can
quickly become tedious. For our planned used cases, we plan to script Foreign tables from a source database for tables that are bulky and rarely change
and then build materialized views against those for faster performance where needed. To help in this end, we wrote a quick
SQL function that you install on the source database and when run will generate foreign table creation statements to install on a target database.
One thing that was important to us was that the function properly handle views and typmod types since PostGIS now uses typmod heavily and many of our databases have spatial data
and complex views we'd like to link in as foreign tables.
Function to generate Foreign table create statements
The function script is shown here and should be installed on the database that you want to script the tables as foreign tables for future use on a target server.
CREATE OR REPLACE FUNCTION script_foreign_tables(param_server text
, param_schema_search text
, param_table_search text, param_ft_prefix text) RETURNS SETOF text
AS
$$
WITH cols AS
( SELECT cl.relname As table_name, na.nspname As table_schema, att.attname As column_name
, format_type(ty.oid,att.atttypmod) AS column_type
, attnum As ordinal_position
FROM pg_attribute att
JOIN pg_type ty ON ty.oid=atttypid
JOIN pg_namespace tn ON tn.oid=ty.typnamespace
JOIN pg_class cl ON cl.oid=att.attrelid
JOIN pg_namespace na ON na.oid=cl.relnamespace
LEFT OUTER JOIN pg_type et ON et.oid=ty.typelem
LEFT OUTER JOIN pg_attrdef def ON adrelid=att.attrelid AND adnum=att.attnum
WHERE
cl.relkind IN('v','r')
AND na.nspname LIKE $2 AND cl.relname LIKE $3
AND cl.relname NOT IN('spatial_ref_sys', 'geometry_columns'
, 'geography_columns', 'raster_columns')
AND att.attnum > 0
AND NOT att.attisdropped
ORDER BY att.attnum )
SELECT 'CREATE FOREIGN TABLE ' || $4 || table_name || ' ('
|| string_agg(quote_ident(column_name) || ' ' || column_type
, ', ' ORDER BY ordinal_position)
|| ')
SERVER ' || quote_ident($1) || ' OPTIONS (schema_name ''' || quote_ident(table_schema)
|| ''', table_name ''' || quote_ident(table_name) || '''); ' As result
FROM cols
GROUP BY table_schema, table_name
$$ language 'sql';
Setting up the Target database with postgres_fdw
The first step is to have a database with a postgres_fdw server and user setup. Basic steps are:
CREATE EXTENSION postgres_fdw;
CREATE SERVER book_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'postgis_in_action');
CREATE USER MAPPING FOR public SERVER
book_server
OPTIONS (user 'book_guest', password 'whatever');
Script tables and views as foreign tables
The next step is to run the function we described on the source server. For this exercise, we just want to script one schema and we want to give the tables a different
name in case we want to create materialized views against these that have the original name.
SELECT script_foreign_tables('book_server', 'ch01', '%', 'ch01.ft_');
The output of the above query looks like this:
CREATE FOREIGN TABLE ch01.ft_highways (gid integer, feature character varying(80), name character varying(120)
, state character varying(2), geom geometry(MultiLineString,2163))
SERVER book_server OPTIONS (schema_name 'ch01', table_name 'highways');
CREATE FOREIGN TABLE ch01.ft_lu_franchises (id character(3), franchise character varying(30))
SERVER book_server OPTIONS (schema_name 'ch01', table_name 'lu_franchises');
CREATE FOREIGN TABLE ch01.ft_restaurants (id integer, franchise character(3), geom geometry(Point,2163))
SERVER book_server OPTIONS (schema_name 'ch01', table_name 'restaurants');
There was a bug in PostGIS 2.1.0 and PostGIS 2.0 < 2.0.5 that prevented foreign tables and materialized views from being output
in geometry_columns and geography_columns views. This is fixed in upcoming PostGIS 2.1.1 and PostGIS 2.0.5.