March 17th, 2013 by depesz | Tags: , , , , , , | 3 comments »
Did it help? If yes - maybe you can help me? Donate BTC to 19zPa5diT2LZqGtTi8f8bfApLn8rw9zBHx

On 10th of March, Tom Lane committed patch:

Support writable foreign tables.
 
This patch adds the core-system infrastructure needed to support updates
on foreign tables, and extends contrib/postgres_fdw to allow updates
against remote Postgres servers.  There's still a great deal of room for
improvement in optimization of remote updates, but at least there's basic
functionality there now.
 
KaiGai Kohei, reviewed by Alexander Korotkov and Laurenz Albe, and rather
heavily revised by Tom Lane.

Since 9.1 we had so called “Foreign Data Wrappers" – generally a way to bring other sources of data to PostgreSQL, as simple tables.

There are multiple wrappers (drivers?) – standard PostgreSQL comes with file_fdw (to access .csv files as tables). In 9.3 we'll get postgres_fdw, and there are many more on PGXN.

The issue was, that up until now, all these tables were read only. But now – the whole subsystem for foreign data supports writes. Of course – it will not work automatically in all fdw sources – you have to have your driver actually support it (file_fdw, as far as I know, currently doesn't support writes). But the new postgres_fdw does.

So, let's see how it works.

First, I connected to source database – this one will have the actual table.

And in there I do:

[source] $ create table some_table (id serial primary key, some_number int4);
CREATE TABLE
 
[source] $ insert into some_table (some_number)
    select i * 15 from generate_series(1,3) i;
INSERT 0 3
 
[source] $ select * from some_table;
 id | some_number
----+-------------
  1 |          15
  2 |          30
  3 |          45
(3 rows)

Nice. Now, in another, test, database, I can create the foreign table:

[test] $ create extension postgres_fdw;
CREATE EXTENSION
 
[test] $ create server source foreign data wrapper postgres_fdw options( dbname 'source' );
CREATE SERVER
 
[test] $ create user mapping for depesz server source options ( user 'depesz' );
CREATE USER MAPPING
 
[test] $ create foreign table table_from_source (
    id int4 not null,
    some_number int4
    ) server source
    options ( table_name 'some_table' );
CREATE FOREIGN TABLE

(The option table_name is needed because my source table is named “some_table", but foreign table has different name – table_from_source.

Anyway, with this in place, I can:

[test] $ \d
                  List of relations
 Schema |       Name        |     Type      | Owner
--------+-------------------+---------------+--------
 public | table_from_source | foreign table | depesz
(1 row)
 
[test] $ select * from table_from_source ;
 id | some_number
----+-------------
  1 |          15
  2 |          30
  3 |          45
(3 rows)
 
[test] $ explain analyze select * from table_from_source ;
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Foreign Scan on table_from_source  (cost=100.00..186.80 rows=2560 width=8) (actual time=0.525..0.526 rows=3 loops=1)
 Total runtime: 1.073 ms
(2 rows)

I can also change the data from test database:

[test] $ insert into table_from_source (id, some_number) values (-1, 100);
INSERT 0 1
 
[test] $ select * from table_from_source ;
 id | some_number
----+-------------
  1 |          15
  2 |          30
  3 |          45
 -1 |         100
(4 rows)
 
[test] $ update table_from_source set id = 4 where id = -1;
UPDATE 1
 
[test] $ select * from table_from_source ;
 id | some_number
----+-------------
  1 |          15
  2 |          30
  3 |          45
  4 |         100
(4 rows)
 
[test] $ delete from table_from_source where id < 4;
DELETE 3
 
[test] $ select * from table_from_source ;
 id | some_number
----+-------------
  4 |         100
(1 row)

And how it looks in the source?

[source] $ select * from some_table;
 id | some_number
----+-------------
  4 |         100
(1 row)

Nice. What's more – remote tables are transactional, like here:

[test] $ begin;
BEGIN
 
[test] *$ delete from table_from_source;
DELETE 1
 
[test] *$ rollback;
ROLLBACK
 
[test] $ select * from table_from_source;
 id | some_number
----+-------------
  4 |         100
(1 row)

The only nitpick I have, is that foreign tables do not show up in tab-completion for write commands (insert/update/delete), but I guess it will be fixed soon(ish).

This is really cool stuff. I do not have any databases that use FDWs currently, but ability to use it for RW access, and not only RO, is huge improvement, and it definitely opens a way for new, interesting, usecases.

  1. 3 comments

  2. # mp
    Mar 18, 2013

    The last example looks very much transactional. The data seems to be still there after the rollback.

  3. Mar 18, 2013

    @MP:
    You are absolutely right, my bad. Must have been tired when I wrote that. Fixed, and thank you.

  4. # Vexator
    Dec 4, 2013

    is there any odbc_fdw for 9.3?

Leave a comment