A Foreign Data Wrapper (FDW) is a feature in database management systems that can communicate with an external data source and access data without physically moving the data on a working local database.
PostgreSQL implements portions of the SQL/MED specification, allowing access to data that resides outside PostgreSQL using regular SQL queries. Such data, which is referred to as foreign data, can be accessed by Foreign Data Wrapper.
Note: SQL/MED is Management of External Data, a part of the SQL standard that deals with how a database management system can integrate data stored outside the database.
We have various Foreign Data wrappers in PostgreSQL like postgres_fdw, file_fdw, Oracle_fdw, Mysql_fdw, TDS_fdw etc. Here in this blog post we will discuss dblink and postgres_fdw.
dblink is an extension that facilitates database sessions in establishing connections to other PostgreSQL databases.
Installation of dblink on a database:
The installation of the dblink extension can be executed directly through the CREATE command, as illustrated below.
|
1 |
dblink_testing=# CREATE EXTENSION dblink;<br>CREATE EXTENSION<br><br>dblink_testing=# dx dblink<br> List of installed extensions<br> Name | Version | Schema | Description <br>--------+---------+--------+--------------------------------------------------------------<br>dblink | 1.2 | public | connect to other PostgreSQL databases from within a database<br>(1 row) |
|
1 |
dblink_testing=# SELECT dblink_connect('pg_conn','hostaddr=*.*.*.* port=5434 dbname=dblink user=dblink_test password=******');<br><br>dblink_connect<br>----------------<br>OK<br>(1 row) |
|
1 |
dblink_testing=# select * from dblink ('pg_conn', 'select nspname as schema, relname as tablename,reltuples as rowcounts from<br>pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace') AS RESULT(schema name,tablename name,rowcounts int) where<br>schema like 'fdw_testing' order by schema, rowcounts desc;<br> schema | tablename | rowcounts <br>-------------+------------+-----------<br>fdw_testing | staff | 1000<br>fdw_testing | staff_pkey | 1000<br>fdw_testing | students | 300<br>(3 rows) |
Below are a few examples shown for executing commands like CREATE TABLE, INSERT data, and CREATE VIEW on the remote database.
|
1 |
dblink_testing=# SELECT dblink_exec('pg_conn','CREATE TABLE fdw_testing.employees (id INT PRIMARY KEY , fname VARCHAR(20) ,<br>lname VARCHAR (20) , dob DATE NOT NULL ,joined DATE )'); <br>dblink_exec <br>--------------<br>CREATE TABLE<br>(1 row) |
|
1 |
dblink_testing=# SELECT dblink_exec('pg_conn','INSERT INTO fdw_testing.employees (id ,fname,lname,dob ,joined) VALUES<br>( generate_series(1, 145) , (array[''Oswald'', ''Henry'', ''Bob'', ''Vennie''])[floor(random() * 4 + 1)],(array[''Leo'', ''Jack'',<br>''Den'', ''Daisy'' ,''Woody'']) [floor(random() * 5 + 1)], ''1995-01-01''::date + trunc(random() * 366 * 3)::int,<br>generate_series(''01/03/2024''::timestamp, ''09/02/2023''::timestamp, ''10 minutes''))');<br>dblink_exec <br>--------------<br>INSERT 0 145<br>(1 row) |
|
1 |
dblink_testing=# select dblink_exec('pg_conn','CREATE OR REPLACE VIEW sample AS SELECT table_catalog,table_schema,table_name FROM<br>information_schema.views WHERE is_insertable_into=''YES''');<br>dblink_exec <br>-------------<br> CREATE VIEW<br>(1 row) |
|
1 |
dblink_testing=# SELECT dblink_exec('pg_conn','INSERT INTO students (id ,fname,lname,dob ,joined) VALUES ( generate_series(500, 1000) ,<br>(array[''Oswald'', ''Henry'', ''Bob'', ''Vennie''])[floor(random() * 4 + 1)],(array[''Leo'', ''Jack'', ''Den'', ''Daisy'' ,''Woody''])[floor(random() * 5 + 1)],<br>''1995-01-01''::date + trunc(random() * 366 * 3)::int,generate_series(''01/03/2024''::timestamp, ''09/02/2023''::timestamp, ''10 minutes''))');<br>ERROR: duplicate key value violates unique constraint "students_pkey"<br>DETAIL: Key (id)=(500) already exists.<br>CONTEXT: while executing command on dblink connection named "pg_conn"<br><br>dblink_testing=# select dblink_error_message('pg_conn');<br> dblink_error_message <br>------------------------------------------------------------------------<br>ERROR: duplicate key value violates unique constraint "students_pkey"+<br>DETAIL: Key (id)=(500) already exists.<br>(1 row) |
|
1 |
dblink_testing=# select dblink_get_connections();<br>dblink_get_connections <br>------------------------<br>{pg_conn}<br>(1 row) |
|
1 |
dblink_testing=# SELECT dblink_disconnect('pg_conn');<br>dblink_disconnect <br>-------------------<br>OK<br>(1 row) |

The postgres_fdw extension offers the foreign-data wrapper postgres_fdw, enabling access to data stored in external PostgreSQL servers.
|
1 |
postgres_fdw_db=# CREATE EXTENSION postgres_fdw ;<br>CREATE EXTENSION<br><br>postgres_fdw_db=# dx postgres_fdw<br> List of installed extensions<br> Name | Version | Schema | Description <br>--------------+---------+--------+----------------------------------------------------<br>postgres_fdw | 1.0 | public | foreign-data wrapper for remote PostgreSQL servers<br>(1 row) |
2. To establish connections to remote (source) databases, you can create a foreign server object using the CREATE SERVER command.
E.g.: CREATE SERVER foreign_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (hostaddr ‘remote_ip_addr’ , port ‘remote_port’ , dbname ‘remote_db);’
|
1 |
postgres_fdw_db=# CREATE SERVER foreign_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (hostaddr '*.*.*.*', port '5434',<br>dbname 'dblink');<br>CREATE SERVER |
Furthermore, you can list foreign servers through the below command.
|
1 |
postgres_fdw_db=# des<br> List of foreign servers<br> Name | Owner | Foreign-data wrapper <br>----------------+----------+----------------------<br>foreign_server | postgres | postgres_fdw<br>(1 row) |
3. For each database user to access each remote server, create a user mapping using the CREATE USER MAPPING command.
E.g.: CREATE USER MAPPING FOR SERVER foreign_server OPTIONS (USER ‘remote_db_user’ , password ‘*****’);
|
1 |
postgres_fdw_db=# CREATE USER MAPPING FOR postgres_fdw_testing SERVER foreign_server OPTIONS (USER 'dblink_test' , password '*******');<br>CREATE USER MAPPING |
Furthermore, to list user mappings, use the below command.
|
1 |
postgres_fdw_db=# deu<br> List of user mappings<br> Server | User name <br>----------------+----------------------<br>foreign_server | postgres_fdw_testing<br>(1 row) |
To create a new foreign table in the current database, use CREATE FOREIGN TABLE
Note: Users must have USAGE privilege on the foreign server to be able to create a foreign table.
Below is the sample example shown for the Creation Foreign Table and accessing the data.
2.1: Here, we see the data from the Remote Database, which we try to access from the Target Database.
|
1 |
dblink=# select nspname as schema, relname as tablename,reltuples as rowcounts from pg_class c JOIN pg_catalog.pg_namespace n ON<br>n.oid = c.relnamespace where relkind='r' and nspname like 'fdw_testing' order by nspname, reltuples desc;<br> schema | tablename | rowcounts <br>-------------+-----------+-----------<br>fdw_testing | staff | 1000<br>fdw_testing | students | 300<br>fdw_testing | employees | 145<br>(3 rows) |
2.2: Below, we create Foreign Table on the target Database and access the data.
|
1 |
postgres_fdw_db=# CREATE FOREIGN TABLE foreign_schema.employee_foreign_table (id bigint , fname varchar(20) , joined date) SERVER<br>foreign_server OPTIONS (schema_name 'fdw_testing' , table_name 'employees');<br>CREATE FOREIGN TABLE |
|
1 |
postgres_fdw_db=# CREATE FOREIGN TABLE foreign_schema.students_foreign_table (id int , lname varchar(20)) SERVER foreign_server<br>OPTIONS (schema_name 'fdw_testing' , table_name 'students'); <br>CREATE FOREIGN TABLE |
As a result, below we see a list of foreign tables created in the foreign_schema.
|
1 |
postgres_fdw_db=# det foreign_schema.* <br> List of foreign tables<br> Schema | Table | Server <br>----------------+------------------------+----------------<br>foreign_schema | employee_foreign_table | foreign_server<br>foreign_schema | students_foreign_table | foreign_server<br>(2 rows) |
2.3: Validation of Source Data with step 2.1 and data copied to the target local database.
|
1 |
postgres_fdw_db=# SELECT employee_foreign_table_count,students_foreign_table_count FROM (SELECT COUNT(*) from foreign_schema.employee_foreign_table)<br>AS employee_foreign_table_count , (SELECT COUNT(*) from foreign_schema.students_foreign_table) AS students_foreign_table_count;<br>employee_foreign_table_count | students_foreign_table_count <br>------------------------------+------------------------------<br>(145) | (300)<br>(1 row) |
The IMPORT FOREIGN SCHEMA command seamlessly integrates table definitions from a foreign server into the local database, facilitating the smooth incorporation of foreign table structures.
3.1: To import all the tables in the remote (source) schema to the local working database, use the below command.
|
1 |
postgres_fdw_db=# IMPORT FOREIGN SCHEMA fdw_testing FROM SERVER foreign_server INTO postgres_fdw_schema;<br>IMPORT FOREIGN SCHEMA |
Below we list Foreign Tables imported from remote source database.
|
1 |
postgres_fdw_db=# det postgres_fdw_schema.*<br> List of foreign tables<br> Schema | Table | Server <br>---------------------+-----------+----------------<br>postgres_fdw_schema | employees | foreign_server<br>postgres_fdw_schema | staff | foreign_server<br>postgres_fdw_schema | students | foreign_server<br>(3 rows) |
Validating the data in the target after importing to the schema postgres_fdw_schema involves ensuring that it matches the source data.
|
1 |
postgres_fdw_db=# select employees_cnt, staff_cnt , students_cnt FROM (select count(*) from postgres_fdw_schema.employees )<br>AS employees_cnt, (select count(*) from postgres_fdw_schema.staff) AS staff_cnt, (select count(*) from postgres_fdw_schema.students)<br>AS students_cnt;<br>employees_cnt | staff_cnt | students_cnt <br>---------------+-----------+--------------<br>(145) | (1000) | (300)<br>(1 row) |
3.2: Import only foreign tables matching one of the given table names using “LIMIT TO” enables selective inclusion of specific foreign tables.
|
1 |
postgres_fdw_db=# IMPORT FOREIGN SCHEMA fdw_testing LIMIT TO (employees) FROM SERVER foreign_server INTO fdw_1;<br>IMPORT FOREIGN SCHEMA |
|
1 |
postgres_fdw_db=# det fdw_1.*<br> List of foreign tables<br>Schema | Table | Server <br>--------+-----------+----------------<br>fdw_1 | employees | foreign_server<br>(1 row) |
Furthermore, validation of copied data into the local working database with remote data (step2.1).
|
1 |
postgres_fdw_db=# select count(*) from fdw_1.employees;<br>count <br>------<br> 145<br>(1 row) |
3.3: Import all the foreign tables excluding the given table names using “EXCEPT” to enable selective exclusion of specific foreign tables.
|
1 |
postgres_fdw_db=# IMPORT FOREIGN SCHEMA fdw_testing EXCEPT (employees) FROM SERVER foreign_server INTO fdw_2;<br>IMPORT FOREIGN SCHEMA |
Below are the tables imported excluding the table “employees” and validation of copied data into the local working database with remote data (step2.1).
|
1 |
postgres_fdw_db=# det fdw_2.*<br> List of foreign tables<br>Schema | Table | Server <br>--------+----------+----------------<br>fdw_2 | staff | foreign_server<br>fdw_2 | students | foreign_server<br>(2 rows) |
|
1 |
postgres_fdw_db=# select students_cnt, staff_cnt FROM (select count(*) from fdw_2.students ) AS students_cnt, (select count(*)<br>from fdw_2.staff) AS staff_cnt;<br>students_cnt | staff_cnt <br>--------------+-----------<br>(300) | (1000)<br>(1 row) |
For more details, please refer to these blog posts.
See why running open source PostgreSQL in-house demands more time, expertise, and resources than most teams expect — and what it means for IT and the business.
Resources
RELATED POSTS