DEV Community

Renato Suero
Renato Suero

Posted on • Originally published at renatosuero.dev.br

Conectando Dois Bancos PostgreSQL com FDW (DataLink do Heroku)

Postado originalmente

É muito comum quando temos 2 sistemas que se relacionam em algum momento um precisar conectar no outro banco de dados. Seja pela aplicação(falarei sobre em outro post) ou mesmo direto numa consulta no banco de dados.

Imagina conseguir fazer uma consulta onde fazemos um inner join usando uma tabela de cada banco de dados, como a seguir estando conectado no banco de dados loja e consultando produto que vem de catalogo:

select * from vendas v inner join produtos p on p.id=v.produto_id  
Enter fullscreen mode Exit fullscreen mode

Quanto usamos serviços como Heroku temos isso via interface(chamado data link) e outros lugares com possíveis outros nomes, mas por baixo dos panos estão usando uma extensão do Postgres chamada [fdw](https://www.postgresql.org/docs/current/postgres-fdw.html)

Meu objetivo é mostrar como conseguimos alcançar isso para o ambiente de desenvolvimento que é onde vamos escrever esse código então precisamos validá-lo

Configuração

Para começar eu vou deixar o script para criação de 2 bancos de dados para seguirmos o tutorial

create database catalogo
-- Cria a tabela 'produtos'
CREATE TABLE produtos (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(255) NOT NULL,
    quantidade INT NOT NULL,
    valor DECIMAL(10, 2) NOT NULL
);

-- Insere uma lista de 10 produtos na tabela
INSERT INTO produtos (nome, quantidade, valor) VALUES
('Notebook Gamer', 15, 7500.50),
('Monitor 4K 27"', 25, 1899.90),
('Teclado Mecânico RGB', 50, 350.00),
('Mouse Sem Fio Ergonômico', 80, 120.75),
('Headset 7.1 Surround', 40, 499.99),
('Cadeira Gamer Confort', 20, 1200.00),
('SSD NVMe 1TB', 60, 850.00),
('Placa de Vídeo RTX 4070', 10, 4500.00),
('Memória RAM 16GB DDR5', 100, 650.25),
('Webcam Full HD', 75, 250.00);
Enter fullscreen mode Exit fullscreen mode
create database loja
-- Cria a tabela 'usuarios'
CREATE TABLE usuarios (
    id SERIAL PRIMARY KEY,
    nome VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE
);

-- Cria a tabela 'vendas'
-- Esta tabela se relaciona com 'usuarios' e 'produtos'
CREATE TABLE vendas (
    id SERIAL PRIMARY KEY,
    usuario_id INT NOT NULL,
    produto_id INT NOT NULL,
    quantidade INT NOT NULL CHECK (quantidade > 0),
    data_venda TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    -- Define as chaves estrangeiras para garantir a integridade dos dados
    CONSTRAINT fk_usuario FOREIGN KEY(usuario_id) REFERENCES usuarios(id)
);
-- II. INSERIR DADOS DE EXEMPLO

-- Insere 10 usuários na tabela 'usuarios'
INSERT INTO usuarios (nome, email) VALUES
('Ana Silva', 'ana.silva@email.com'),
('Bruno Costa', 'bruno.costa@email.com'),
('Carlos Pereira', 'carlos.pereira@email.com'),
('Daniela Martins', 'daniela.martins@email.com'),
('Eduardo Almeida', 'eduardo.almeida@email.com'),
('Fernanda Lima', 'fernanda.lima@email.com'),
('Gustavo Oliveira', 'gustavo.oliveira@email.com'),
('Helena Souza', 'helena.souza@email.com'),
('Igor Santos', 'igor.santos@email.com'),
('Juliana Ribeiro', 'juliana.ribeiro@email.com');

-- Insere 50 registros de vendas na tabela 'vendas'
-- As datas são variadas para simular um histórico de vendas.
INSERT INTO vendas (usuario_id, produto_id, quantidade, data_venda) VALUES
(1, 3, 1, '2025-07-01 10:30:00'),
(5, 1, 1, '2025-07-01 11:15:00'),
(2, 5, 2, '2025-07-02 14:00:00'),
(10, 8, 1, '2025-07-03 09:05:00'),
(3, 7, 1, '2025-07-04 18:20:00'),
(7, 2, 1, '2025-07-05 12:00:00'),
(4, 10, 3, '2025-07-06 16:45:00'),
(8, 4, 1, '2025-07-07 11:50:00'),
(6, 6, 1, '2025-07-08 13:10:00'),
(9, 9, 2, '2025-07-09 10:00:00'),
(1, 4, 1, '2025-07-10 15:00:00'),
(2, 8, 1, '2025-07-11 17:25:00'),
(5, 2, 1, '2025-07-11 19:00:00'),
(7, 1, 1, '2025-07-12 08:30:00'),
(3, 9, 4, '2025-07-13 11:40:00'),
(10, 5, 1, '2025-07-14 20:00:00'),
(4, 7, 1, '2025-07-15 21:05:00'),
(6, 3, 2, '2025-07-16 09:15:00'),
(8, 6, 1, '2025-07-17 10:00:00'),
(1, 10, 1, '2025-07-18 14:55:00'),
(9, 1, 1, '2025-07-19 16:20:00'),
(5, 5, 1, '2025-07-20 18:30:00'),
(2, 7, 2, '2025-07-21 12:10:00'),
(7, 4, 3, '2025-07-22 13:00:00'),
(3, 2, 1, '2025-07-22 14:40:00'),
(8, 8, 1, '2025-07-23 09:00:00'),
(4, 9, 1, '2025-07-23 11:20:00'),
(10, 3, 1, '2025-07-24 16:00:00'),
(6, 1, 1, '2025-07-24 17:00:00'),
(1, 6, 1, '2025-07-25 19:10:00'),
(2, 10, 2, '2025-07-26 10:45:00'),
(5, 4, 1, '2025-07-26 11:00:00'),
(9, 5, 1, '2025-07-26 15:30:00'),
(7, 7, 1, '2025-07-27 13:25:00'),
(3, 1, 1, '2025-07-27 14:00:00'),
(4, 8, 1, '2025-07-28 10:10:00'),
(8, 2, 2, '2025-07-28 11:30:00'),
(10, 9, 1, '2025-07-28 12:00:00'),
(6, 5, 1, '2025-07-29 18:00:00'),
(1, 7, 1, '2025-07-29 18:45:00'),
(9, 3, 3, '2025-07-29 20:15:00'),
(5, 6, 1, '2025-07-30 09:30:00'),
(2, 4, 1, '2025-07-30 10:00:00'),
(7, 10, 1, '2025-07-30 14:20:00'),
(3, 8, 1, '2025-07-30 15:00:00'),
(4, 1, 1, '2025-07-30 16:10:00'),
(8, 9, 2, '2025-07-30 17:40:00'),
(10, 2, 1, '2025-07-31 08:50:00'),
(6, 7, 1, '2025-07-31 09:00:00'),
(1, 5, 1, '2025-07-31 09:15:00');
Enter fullscreen mode Exit fullscreen mode

Agora que temos 2 bancos de dados, podemos começar.Temos alguns passos para configurar a conexão, sendo eles:

Criar a extensão

loja=# CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE EXTENSION
Enter fullscreen mode Exit fullscreen mode

Checando se tudo está ok

loja=# select * from pg_extension;
   oid   |   extname    | extowner | extnamespace | extrelocatable | extversion | extconfig | extcondition
---------+--------------+----------+--------------+----------------+------------+-----------+--------------
   13895 | plpgsql      |       10 |           11 | f              | 1.0        |           |
 7549345 | postgres_fdw |       10 |         2200 | t              | 1.1        |           |
(2 rows)
Enter fullscreen mode Exit fullscreen mode

Criar o servidor no banco de dados:

Criei um server chamado foreigndb_fdw, vamos usar esse nome na hora de importar as tabelas, tbm fiz a configuração para o outro banco de dados, basicamente mudou o dbname já que ambos rodam localmente

loja=# CREATE SERVER foreigndb_fdw FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '127.0.0.1', port '5432', dbname 'catalogo');
CREATE SERVER
Enter fullscreen mode Exit fullscreen mode

Checando se tudo está ok

loja=# \des
               List of foreign servers
     Name      |    Owner     | Foreign-data wrapper
---------------+--------------+----------------------
 foreigndb_fdw | fdwuser      | postgres_fdw
(1 row)
Enter fullscreen mode Exit fullscreen mode

Criar user mapping:

Podemos fazer manual trocando fdwuser para o usuário que vamos conectar ou usar o próximo comando que vai selecionar o usuário conectado 

Eu já tenho um usuário chamado fdwuser, então uso o mesmo assim não preciso configurar permissões e outras configurações, como é um ambiente local podemos deixar isso simples. No primeiro comando eu passo a mesma senha que uso para conectar, na segunda opção não há senha então só criar o mapping do usuário

loja=# CREATE USER MAPPING FOR fdwuser SERVER foreigndb_fdw OPTIONS (user 'fdwuser', password 'secret');
CREATE USER MAPPING
Enter fullscreen mode Exit fullscreen mode

Comando lendo o usuário logado, o usuário que estamos executando os comandos

SELECT format('CREATE USER MAPPING FOR %I SERVER foreigndb_fdw OPTIONS (user %L)', current_user, current_user) \gexec
Enter fullscreen mode Exit fullscreen mode

Checando se tudo está ok

loja=# select * from pg_user_mapping;
   oid   | umuser | umserver |      umoptions
---------+--------+----------+---------------------
 7549353 |     10 |  7549352 | {user=fdwuser}
(1 row)
loja=# select * from pg_user_mappings;
  umid   |  srvid  |    srvname    | umuser |   usename    |      umoptions
---------+---------+---------------+--------+--------------+---------------------
 7549353 | 7549352 | foreigndb_fdw |     10 | fdwuser      | {user=fdwuser}
(1 row)
Enter fullscreen mode Exit fullscreen mode

Depois que fizemos as configurações agora falta um último passo, importar as tabelas utilizadas no banco de dados. Aqui eu tenho algumas notas que farei na sequência, mas vou deixar um exemplo de como funcionaria.

loja=# IMPORT FOREIGN SCHEMA public                                                                                                             FROM SERVER foreigndb_fdw                                                                                                                     INTO public;
IMPORT FOREIGN SCHEMA
Enter fullscreen mode Exit fullscreen mode

Notas sobre o import

  • Sempre que criar novas tabelas precisará fazer o import para que traga a nova tabela;
  • Caso tenha conflito com nome de tabela, ambos banco de dados tendo uma tabela com o mesmo nome, podemos criar um schema. Note que que comando acima é feito no final INTO public  troque public para o nome do novo schema, e na hora da consulta tem que fazer nome-schema.tabela  se criamos um schema catalogo seria assim catalogo.produtos  
  • O import não traz enum ou coisas do tipo, neste caso eu copio o script de criação e executo no outro banco de dados, assim ele funcionará porque agora terá esses items customizados
  • Eu fiz um import de todas tabelas, mas podemos restringir com algumas opções:
    • EXCEPT para excluir tabelas,  IMPORT FOREIGN SCHEMA public EXCEPT (produtos)
    • LIMIT TO para selecionar apenas algumas  IMPORT FOREIGN SCHEMA public LIMIT TO (produtos)

Deletando tudo

Hora de limpar a casa, detelando a extensão já irá remover as configurações de server, users mappings e o import das tabelas.

loja=# DROP EXTENSION IF EXISTS postgres_fdw CASCADE;
NOTICE:  drop cascades to 3 other objects
DETAIL:  drop cascades to server foreigndb_fdw
drop cascades to user mapping for renato.suero on server foreigndb_fdw
drop cascades to foreign table produtos
DROP EXTENSION
Enter fullscreen mode Exit fullscreen mode

Top comments (0)