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.
Panggilan Ad-hoc dengan dblink() #
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:
DROP DATABASE
CREATE DATABASE
You are now connected to database "gudang_lain" as user "postgres".
CREATE TABLE
INSERT 0 4Dengan 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:
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:
| Opsi | Peran | Contoh nilai |
|---|---|---|
dbname | Database tujuan | gudang_lain |
host | Hostname atau direktori socket | db.internal, /var/run/postgresql |
port | Port server tujuan | 5432 |
user dan password | Kredensial untuk database tujuan | budi, 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:
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.
Koneksi Persistent dengan dblink_connect() #
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:
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_exec untuk DDL dan DML #
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:
-- 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:
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.
dblink atau postgres_fdw? #
Keduanya sama-sama menjembatani database PostgreSQL, tetapi dirancang untuk zaman yang berbeda. Tabel berikut merangkum perbedaan yang menentukan pilihan:
| Aspek | dblink | postgres_fdw |
|---|---|---|
| Model koneksi | Ad-hoc per panggilan, atau persistent bernama per sesi | Terkelola lewat server + user mapping di katalog |
| Bentuk hasil | Daftar kolom manual AS t(...) di setiap pemanggilan | Foreign table bertipe — didefinisikan sekali |
| DML/DDL remote | String SQL via dblink_exec() | INSERT/UPDATE/DELETE langsung pada foreign table |
| Join dengan tabel lokal | Manual — hasil dblink menjadi subquery | Transparan — planner ikut merencanakan |
| Objek yang tertinggal | Tidak ada | SERVER, USER MAPPING, foreign table |
| Panggilan dari dalam fungsi | Natural — semuanya fungsi SQL | Tidak 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:
DROP EXTENSIONFile 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:
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 DATABASEDibaca 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-- 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-- 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-- 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-- 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-- 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-- 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-- 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);