Chapter sebelumnya menuntut persiapan: CREATE SERVER, CREATE USER MAPPING, dan CREATE FOREIGN TABLE dideklarasikan dulu agar data lintas database bisa dipakai berulang kali. Namun kebutuhan nyata sering jauh lebih kecil — menarik satu daftar stok dari database gudang untuk sekadar ditampilkan, atau menjalankan satu UPDATE di database tetangga dari dalam sebuah fungsi. Untuk kebutuhan sekali-jalan itulah extension dblink hadir jauh lebih tua dari postgres_fdw: seluruh konektivitas dibungkus pemanggilan fungsi SQL — koneksi dibuka, query dikirim, hasil diterima, selesai — tanpa satu pun objek katalog yang tersisa. Chapter ini membedah API-nya dari panggilan ad-hoc hingga koneksi persistent, lalu menutup dengan perbandingan jujur melawan postgres_fdw.

Fungsi dblink() menerima dua argumen string: connection string gaya libpq dan query SQL yang akan dijalankan di database tujuan. Hasilnya berupa set baris, tetapi ada satu kewajiban khas dblink yang tidak dimiliki postgres_fdw — klausa AS t(kolom tipe, ...): pemanggil harus mendeklarasikan sendiri nama dan tipe setiap kolom hasilnya. Demo kita memakai dua database seperti chapter sebelumnya, kali ini dengan database gudang_lain sebagai pemilik data stok:

-- Demo dblink membutuhkan database kedua: "gudang_lain" berperan sebagai
-- database gudang pemilik data stok, sementara database kerja chapter ini
-- berperan sebagai aplikasi toko yang perlu membacanya. CREATE DATABASE
-- dijalankan dari database maintenance karena tidak bisa dilakukan dari
-- dalam database yang sedang dipakai.
DROP DATABASE IF EXISTS gudang_lain;

CREATE DATABASE gudang_lain;

-- Pindah ke database gudang, lalu bangun tabel stoknya.
\connect gudang_lain

CREATE TABLE stok_barang (
    kode text    PRIMARY KEY,
    nama text    NOT NULL,
    stok integer NOT NULL
);

INSERT INTO stok_barang VALUES
    ('BRG-001', 'Beras Premium 5kg', 120),
    ('BRG-002', 'Minyak Goreng 1L',   85),
    ('BRG-003', 'Gula Pasir 1kg',    200),
    ('BRG-004', 'Tepung Terigu 1kg',  60);

Output yang Dihasilkan:

text
DROP DATABASE
CREATE DATABASE
You are now connected to database "gudang_lain" as user "postgres".
CREATE TABLE
INSERT 0 4

Dengan database gudang berdiri, file berikut memasang dblink lalu melakukan dua panggilan ad-hoc dari sisi toko:

-- dblink dipasang di database toko (database kerja chapter ini).
CREATE EXTENSION dblink;

-- Panggilan ad-hoc: string koneksi DAN query SQL dikirim dalam satu
-- pemanggilan. Klausa AS t(kolom tipe, ...) wajib -- dblink mengembalikan
-- SETOF record yang bentuknya harus dideklarasikan oleh pemanggil.
SELECT * FROM dblink(
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain',
    'SELECT kode, nama, stok FROM stok_barang WHERE stok < 100 ORDER BY kode'
) AS t(kode text, nama text, stok integer);

-- Panggilan kedua membuka koneksi BARU lalu menutupnya lagi:
-- tidak ada objek atau state yang tersisa di database lokal.
SELECT * FROM dblink(
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain',
    'SELECT count(*) AS total_barang FROM stok_barang'
) AS t(total_barang bigint);

Output yang Dihasilkan:

text
CREATE EXTENSION
  kode   |       nama        | stok 
---------+-------------------+------
 BRG-002 | Minyak Goreng 1L  |   85
 BRG-004 | Tepung Terigu 1kg |   60
(2 rows)

 total_barang 
--------------
            4
(1 row)

Query pertama mengembalikan dua barang dengan stok di bawah 100, dan query kedua menghitung empat barang total — semuanya berasal dari gudang_lain tanpa satu objek katalog pun dibuat di database toko. String koneksinya sendiri adalah connection string libpq yang sama dengan yang dipakai psql saat startup:

OpsiPeranContoh nilai
dbnameDatabase tujuangudang_lain
hostHostname atau direktori socketdb.internal, /var/run/postgresql
portPort server tujuan5432
user dan passwordKredensial untuk database tujuanbudi, YOUR_PASSWORD

Satu detail yang sering menjebak: koneksi dblink dibuka oleh proses server (backend), bukan oleh klien psql. Variabel lingkungan PGHOST atau PGPORT yang berlaku untuk klien Anda tidak diwarisi backend, sehingga server kursus menuliskan host dan port secara eksplisit di setiap string koneksi. Di server produksi dengan socket dan port standar, cukup dbname=gudang_lain user=postgres — bentuk yang paling sering tampil di dokumentasi.

Kewajiban Daftar Definisi Kolom #

Apa yang terjadi bila klausa AS t(...) dilupakan? PostgreSQL menolak statement tersebut di tahap parsing — fungsi dblink mengembalikan SETOF record tanpa informasi struktur, dan tanpa deklarasi kolom, server tidak tahu bentuk hasilnya:

-- Tanpa daftar definisi kolom, PostgreSQL menolak: hasil dblink berupa
-- record yang struktur kolomnya tidak diketahui oleh server lokal.
SELECT * FROM dblink(
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain',
    'SELECT kode, nama FROM stok_barang'
);

Output yang Dihasilkan:

text
psql:error-tanpa-definisi.sql:6: ERROR:  a column definition list is required for functions returning "record"
LINE 1: SELECT * FROM dblink(
                      ^

Konsekuensi kewajiban ini nyata di pemakaian sehari-hari: setiap pemanggilan dblink mengulang definisi kolomnya, dan definisi yang tidak cocok dengan hasil nyata — misalnya stok integer padahal kolom remote bertipe bigint — baru meledak saat runtime dengan error type mismatch, bukan saat statement disusun. Foreign table postgres_fdw menyimpan definisi sekali di katalog; dblink menagihnya setiap panggilan.

Memasang string koneksi di setiap panggilan boros bila satu alur kerja membuka banyak query ke database yang sama. Pasangan dblink_connect() dan dblink_disconnect() mengubah modelnya: koneksi dibuka sekali dengan nama, bertahan sepanjang sesi SQL, lalu semua pemanggilan berikutnya cukup menyebut nama tersebut. File berikut sekaligus membuktikan bahwa koneksi itu benar-benar dipertahankan — tabel TEMP yang dibuat di sesi remote tetap terbaca oleh pemanggilan dblink berikutnya:

-- dblink_connect membuka koneksi yang bertahan sepanjang sesi SQL.
-- Nama 'konek_gudang' dipakai pemanggilan berikutnya tanpa string koneksi.
SELECT dblink_connect('konek_gudang',
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain');

-- Fungsi ini mendaftar koneksi aktif milik sesi kita.
SELECT * FROM dblink_get_connections();

-- Query berikutnya cukup menyebut nama koneksinya.
SELECT * FROM dblink('konek_gudang',
    'SELECT kode, stok FROM stok_barang ORDER BY kode'
) AS t(kode text, stok integer);

-- Bukti koneksi yang sama dipertahankan antar panggilan: tabel TEMP dibuat
-- lewat dblink_exec di sesi remote, lalu dibaca kembali lewat dblink().
-- Tabel temp hanya hidup selama sesi remote -- query ini gagal bila setiap
-- panggilan membuka koneksi baru.
SELECT dblink_exec('konek_gudang',
    'CREATE TEMP TABLE keranjang (kode text, jumlah integer)');

SELECT dblink_exec('konek_gudang',
    'INSERT INTO keranjang VALUES (''BRG-001'', 10)');

SELECT * FROM dblink('konek_gudang',
    'SELECT kode, jumlah FROM keranjang ORDER BY kode'
) AS t(kode text, jumlah integer);

-- Putuskan sambungan; daftar koneksi sesi ini kembali kosong.
SELECT dblink_disconnect('konek_gudang');

SELECT * FROM dblink_get_connections();

Output yang Dihasilkan:

text
 dblink_connect 
----------------
 OK
(1 row)

 dblink_get_connections 
------------------------
 {konek_gudang}
(1 row)

  kode   | stok 
---------+------
 BRG-001 |  120
 BRG-002 |   85
 BRG-003 |  200
 BRG-004 |   60
(4 rows)

 dblink_exec  
--------------
 CREATE TABLE
(1 row)

 dblink_exec 
-------------
 INSERT 0 1
(1 row)

  kode   | jumlah 
---------+--------
 BRG-001 |     10
(1 row)

 dblink_disconnect 
-------------------
 OK
(1 row)

 dblink_get_connections 
------------------------
 
(1 row)

Urutan outputnya membaca seperti cerita: dblink_connect menjawab OK, dblink_get_connections() mendaftar array {konek_gudang}, query stok berjalan dengan hanya menyebut nama koneksi, lalu CREATE TABLE dan INSERT 0 1 dari dblink_exec membuat tabel temporary keranjang di sesi remote. SELECT terakhir masih bisa membaca keranjang — bukti pemanggilan-pemanggilan itu mendarat di sesi remote yang sama; bila dblink membuka koneksi baru setiap kali, tabel temp itu sudah lenyap dan query gagal dengan relation does not exist. Setelah dblink_disconnect menjawab OK, daftar koneksi kembali kosong — hasil dblink_get_connections() yang kosong itu tampil sebagai baris NULL.

dblink() mengembalikan baris, sehingga statement tanpa hasil — CREATE TABLE, INSERT, UPDATE, GRANT — butuh jalur lain: dblink_exec(). Fungsi ini mengirim satu statement string ke koneksi bernama dan melaporkan kembali tag command-nya sebagai teks:

sql dblink-exec.sql
-- File SQL baru = sesi psql baru: koneksi dari file sebelumnya sudah
-- tertutup saat sesinya berakhir, sehingga kita menyambung ulang.
SELECT dblink_connect('konek_gudang',
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain');

-- dblink_exec menjalankan statement yang tidak mengembalikan baris:
-- DDL dan DML dikirim sebagai string SQL, hasilnya berupa tag command.
SELECT dblink_exec('konek_gudang',
    'CREATE TABLE permintaan_barang (
        kode   text PRIMARY KEY,
        jumlah integer NOT NULL
     )');

SELECT dblink_exec('konek_gudang',
    'INSERT INTO permintaan_barang VALUES (''BRG-003'', 40)');

-- UPDATE stok di sisi gudang ...
SELECT dblink_exec('konek_gudang',
    'UPDATE stok_barang SET stok = stok - 40 WHERE kode = ''BRG-003''');

-- ... lalu verifikasi hasilnya dari sisi toko, masih di sesi koneksi yang sama.
SELECT * FROM dblink('konek_gudang',
    'SELECT kode, stok FROM stok_barang ORDER BY kode'
) AS t(kode text, stok integer);

SELECT * FROM dblink('konek_gudang',
    'SELECT kode, jumlah FROM permintaan_barang ORDER BY kode'
) AS t(kode text, jumlah integer);

SELECT dblink_disconnect('konek_gudang');

Output yang Dihasilkan:

text
 dblink_connect 
----------------
 OK
(1 row)

 dblink_exec  
--------------
 CREATE TABLE
(1 row)

 dblink_exec 
-------------
 INSERT 0 1
(1 row)

 dblink_exec 
-------------
 UPDATE 1
(1 row)

  kode   | stok 
---------+------
 BRG-001 |  120
 BRG-002 |   85
 BRG-003 |  160
 BRG-004 |   60
(4 rows)

  kode   | jumlah 
---------+--------
 BRG-003 |     40
(1 row)

 dblink_disconnect 
-------------------
 OK
(1 row)

Tiga tag CREATE TABLE, INSERT 0 1, dan UPDATE 1 pada output adalah tag command dari sisi gudang yang diteruskan sebagai teks — bentuk yang sama dengan yang psql cetak untuk statement lokal. Verifikasi berikutnya memperlihatkan efek kumulatifnya: stok BRG-003 turun dari 200 menjadi 160, dan tabel permintaan_barang yang baru saja lahir lewat string SQL kini terbaca dengan isinya. Baris pertama file juga penting dicatat: file SQL baru berarti sesi psql baru, dan koneksi bernama dari file sebelumnya sudah tertutup bersama sesinya — penyambungan ulang itu bukan kebetulan, melainkan konsekuensi umur koneksi yang per-sesi.

Keduanya sama-sama menjembatani database PostgreSQL, tetapi dirancang untuk zaman yang berbeda. Tabel berikut merangkum perbedaan yang menentukan pilihan:

Aspekdblinkpostgres_fdw
Model koneksiAd-hoc per panggilan, atau persistent bernama per sesiTerkelola lewat server + user mapping di katalog
Bentuk hasilDaftar kolom manual AS t(...) di setiap pemanggilanForeign table bertipe — didefinisikan sekali
DML/DDL remoteString SQL via dblink_exec()INSERT/UPDATE/DELETE langsung pada foreign table
Join dengan tabel lokalManual — hasil dblink menjadi subqueryTransparan — planner ikut merencanakan
Objek yang tertinggalTidak adaSERVER, USER MAPPING, foreign table
Panggilan dari dalam fungsiNatural — semuanya fungsi SQLTidak lazim

Rekomendasi modern cukup tegas: untuk kebutuhan berulang dan pemakaian produksi, postgres_fdw menang hampir di semua aspek — tipe terkelola, pushdown predikat oleh planner, dan join yang direncanakan sebagai satu kesatuan. dblink tetap punya tempat untuk panggilan sekali-jalan di dalam stored function, skrip maintenance lintas database, dan investigasi cepat saat debugging — situasi di mana justru ketiadaan objek katalog menjadi nilai jualnya.

Membersihkan Jejak #

Pembersihan dblink memang yang paling sederhana di seluruh section ini — tidak ada server, mapping, atau foreign table yang harus dijatuhkan berurutan:

-- dblink tidak meninggalkan objek katalog: tidak ada server, user mapping,
-- atau foreign table yang perlu dijatuhkan lebih dulu. Cukup lepas
-- extension-nya.
DROP EXTENSION dblink;

Output yang Dihasilkan:

text
DROP EXTENSION

File terakhir menutup siklus dua database dengan membuktikan hasil dblink_exec benar-benar tersimpan di gudang, lalu menjatuhkan database-nya:

-- Bukti hasil dblink_exec memang tersimpan di database gudang:
-- sambungan langsung tanpa perantara dblink.
\connect gudang_lain

SELECT kode, stok
FROM stok_barang
WHERE kode = 'BRG-003';

SELECT kode, jumlah
FROM permintaan_barang
ORDER BY kode;

-- Kembali ke database maintenance, lalu jatuhkan database gudang demo.
\connect postgres

DROP DATABASE gudang_lain;

Output yang Dihasilkan:

text
You are now connected to database "gudang_lain" as user "postgres".
  kode   | stok 
---------+------
 BRG-003 |  160
(1 row)

  kode   | jumlah 
---------+--------
 BRG-003 |     40
(1 row)

You are now connected to database "postgres" as user "postgres".
DROP DATABASE

Dibaca langsung dari gudang_lain, stok BRG-003 memang 160 dan permintaan_barang memang berisi satu baris — dua fakta yang tadi kita ubah sepenuhnya lewat string SQL dari sisi toko. Lintasi data selesai; kita berpindah ke jenis extension yang sama sekali berbeda wujudnya. PostGIS tidak menambah konektivitas, melainkan mengubah cara PostgreSQL memahami ruang: koordinat, jarak, dan wilayah menjadi tipe data kelas satu — chapter berikutnya memetakan kemampuannya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

bersih-bersih.sql

Unduh berkas
sql
-- dblink tidak meninggalkan objek katalog: tidak ada server, user mapping,
-- atau foreign table yang perlu dijatuhkan lebih dulu. Cukup lepas
-- extension-nya.
DROP EXTENSION dblink;

buat-database-gudang.sql

Unduh berkas
sql
-- Demo dblink membutuhkan database kedua: "gudang_lain" berperan sebagai
-- database gudang pemilik data stok, sementara database kerja chapter ini
-- berperan sebagai aplikasi toko yang perlu membacanya. CREATE DATABASE
-- dijalankan dari database maintenance karena tidak bisa dilakukan dari
-- dalam database yang sedang dipakai.
DROP DATABASE IF EXISTS gudang_lain;

CREATE DATABASE gudang_lain;

-- Pindah ke database gudang, lalu bangun tabel stoknya.
\connect gudang_lain

CREATE TABLE stok_barang (
    kode text    PRIMARY KEY,
    nama text    NOT NULL,
    stok integer NOT NULL
);

INSERT INTO stok_barang VALUES
    ('BRG-001', 'Beras Premium 5kg', 120),
    ('BRG-002', 'Minyak Goreng 1L',   85),
    ('BRG-003', 'Gula Pasir 1kg',    200),
    ('BRG-004', 'Tepung Terigu 1kg',  60);

dblink-exec.sql

Unduh berkas
sql
-- File SQL baru = sesi psql baru: koneksi dari file sebelumnya sudah
-- tertutup saat sesinya berakhir, sehingga kita menyambung ulang.
SELECT dblink_connect('konek_gudang',
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain');

-- dblink_exec menjalankan statement yang tidak mengembalikan baris:
-- DDL dan DML dikirim sebagai string SQL, hasilnya berupa tag command.
SELECT dblink_exec('konek_gudang',
    'CREATE TABLE permintaan_barang (
        kode   text PRIMARY KEY,
        jumlah integer NOT NULL
     )');

SELECT dblink_exec('konek_gudang',
    'INSERT INTO permintaan_barang VALUES (''BRG-003'', 40)');

-- UPDATE stok di sisi gudang ...
SELECT dblink_exec('konek_gudang',
    'UPDATE stok_barang SET stok = stok - 40 WHERE kode = ''BRG-003''');

-- ... lalu verifikasi hasilnya dari sisi toko, masih di sesi koneksi yang sama.
SELECT * FROM dblink('konek_gudang',
    'SELECT kode, stok FROM stok_barang ORDER BY kode'
) AS t(kode text, stok integer);

SELECT * FROM dblink('konek_gudang',
    'SELECT kode, jumlah FROM permintaan_barang ORDER BY kode'
) AS t(kode text, jumlah integer);

SELECT dblink_disconnect('konek_gudang');

error-tanpa-definisi.sql

Unduh berkas
sql
-- Tanpa daftar definisi kolom, PostgreSQL menolak: hasil dblink berupa
-- record yang struktur kolomnya tidak diketahui oleh server lokal.
SELECT * FROM dblink(
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain',
    'SELECT kode, nama FROM stok_barang'
);

hapus-database-gudang.sql

Unduh berkas
sql
-- Bukti hasil dblink_exec memang tersimpan di database gudang:
-- sambungan langsung tanpa perantara dblink.
\connect gudang_lain

SELECT kode, stok
FROM stok_barang
WHERE kode = 'BRG-003';

SELECT kode, jumlah
FROM permintaan_barang
ORDER BY kode;

-- Kembali ke database maintenance, lalu jatuhkan database gudang demo.
\connect postgres

DROP DATABASE gudang_lain;

koneksi-persistent.sql

Unduh berkas
sql
-- dblink_connect membuka koneksi yang bertahan sepanjang sesi SQL.
-- Nama 'konek_gudang' dipakai pemanggilan berikutnya tanpa string koneksi.
SELECT dblink_connect('konek_gudang',
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain');

-- Fungsi ini mendaftar koneksi aktif milik sesi kita.
SELECT * FROM dblink_get_connections();

-- Query berikutnya cukup menyebut nama koneksinya.
SELECT * FROM dblink('konek_gudang',
    'SELECT kode, stok FROM stok_barang ORDER BY kode'
) AS t(kode text, stok integer);

-- Bukti koneksi yang sama dipertahankan antar panggilan: tabel TEMP dibuat
-- lewat dblink_exec di sesi remote, lalu dibaca kembali lewat dblink().
-- Tabel temp hanya hidup selama sesi remote -- query ini gagal bila setiap
-- panggilan membuka koneksi baru.
SELECT dblink_exec('konek_gudang',
    'CREATE TEMP TABLE keranjang (kode text, jumlah integer)');

SELECT dblink_exec('konek_gudang',
    'INSERT INTO keranjang VALUES (''BRG-001'', 10)');

SELECT * FROM dblink('konek_gudang',
    'SELECT kode, jumlah FROM keranjang ORDER BY kode'
) AS t(kode text, jumlah integer);

-- Putuskan sambungan; daftar koneksi sesi ini kembali kosong.
SELECT dblink_disconnect('konek_gudang');

SELECT * FROM dblink_get_connections();

pasang-dan-adhoc.sql

Unduh berkas
sql
-- dblink dipasang di database toko (database kerja chapter ini).
CREATE EXTENSION dblink;

-- Panggilan ad-hoc: string koneksi DAN query SQL dikirim dalam satu
-- pemanggilan. Klausa AS t(kolom tipe, ...) wajib -- dblink mengembalikan
-- SETOF record yang bentuknya harus dideklarasikan oleh pemanggil.
SELECT * FROM dblink(
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain',
    'SELECT kode, nama, stok FROM stok_barang WHERE stok < 100 ORDER BY kode'
) AS t(kode text, nama text, stok integer);

-- Panggilan kedua membuka koneksi BARU lalu menutupnya lagi:
-- tidak ada objek atau state yang tersisa di database lokal.
SELECT * FROM dblink(
    'host=/home/z/my-project/pgrun port=54329 user=postgres dbname=gudang_lain',
    'SELECT count(*) AS total_barang FROM stok_barang'
) AS t(total_barang bigint);