Chapter sebelumnya memperlihatkan extension sebagai bundel control file, script SQL, dan shared library. Sekarang kita masuk ke praktik pengelolaannya. Pemasangan extension terjadi di dua lapisan yang berbeda: paket harus tersedia di sistem operasi server lebih dulu, baru kemudian CREATE EXTENSION menjalankan script-nya di level SQL. Kedua lapisan itu sering membingungkan pemula — error yang sama sekali tidak informatif muncul bila urutannya terbalik. Chapter ini menutup seluruh siklus hidup: memeriksa ketersediaan, memasang dengan atau tanpa versi tertentu, memperbarui versi, menempatkan objek di schema khusus, dan melepas extension dengan segala konsekuensi CASCADE-nya.

Dua Langkah Pemasangan: Paket OS, Lalu SQL #

Langkah pertama berlangsung di luar PostgreSQL. Di Debian/Ubuntu, extension buatan komunitas PGDG didistribusikan lewat paket postgresql-contrib-17 untuk extension bawaan, atau paket terpisah seperti postgresql-17-postgis-3 untuk PostGIS; di RHEL keluarganya bernama postgresql-contrib. Setelah paket terpasang dan server di-restart bila perlu, control file baru akan terbaca dan extension muncul di katalog. Langkah kedua — CREATE EXTENSION — baru bisa berhasil setelah langkah pertama. File berikut mencoba melompati langkah pertama untuk extension yang paketnya tidak ada di server kursus:

-- pgbuffercache tidak ada di pg_available_extensions server kursus
-- karena paketnya tidak ikut dipasang di sistem operasi.
CREATE EXTENSION pgbuffercache;

Output yang Dihasilkan:

text
psql:error-paket-hilang.sql:3: ERROR:  extension "pgbuffercache" is not available
DETAIL:  Could not open extension control file "/home/z/my-project/tools/dl/pgdeb/usr/share/postgresql/17/extension/pgbuffercache.control": No such file or directory.
HINT:  The extension must first be installed on the system where PostgreSQL is running.

Pesan errornya justru paling informatif untuk kasus ini: DETAIL-nya memperlihatkan path control file yang gagal dibuka, dan HINT-nya menyebut tegas bahwa extension harus dipasang lebih dulu di sistem tempat PostgreSQL berjalan — bukan di mesin klien, bukan lewat SQL. Bila suatu saat Anda menemui error serupa untuk PostGIS atau pg_partman, penanganannya satu: pasang paketnya di server, lalu ulangi CREATE EXTENSION.

Memeriksa yang Tersedia vs yang Terpasang #

Dua katalog bekerja bersama di sini: pg_available_extensions berisi semua extension yang bisa dipasang (dibaca dari control file), sedangkan pg_extension berisi yang sedang terpasang di database aktif. Kolom installed_version di view pertama menjembatani keduanya — NULL berarti belum terpasang. Perintah meta \dx di psql menampilkan ringkasan yang sama dalam format siap baca:

-- Extension yang TERPASANG di database ini (katalog pg_extension).
\dx

-- Bentuk SQL-nya: katalog pg_extension berisi extension aktif.
SELECT extname, extversion, extnamespace::regnamespace AS skema
FROM pg_extension
ORDER BY extname;

-- Extension yang TERSEDIA namun belum terpasang: kolom installed_version NULL.
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE name IN ('pgcrypto', 'pg_trgm', 'citext', 'amcheck')
ORDER BY name;

Output yang Dihasilkan:

text
                 List of installed extensions
  Name   | Version |   Schema   |         Description          
---------+---------+------------+------------------------------
 plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
(1 row)

 extname | extversion |   skema    
---------+------------+------------
 plpgsql | 1.0        | pg_catalog
(1 row)

   name   | default_version | installed_version 
----------+-----------------+-------------------
 amcheck  | 1.4             | 
 citext   | 1.6             | 
 pg_trgm  | 1.6             | 
 pgcrypto | 1.3             | 
(4 rows)

Database segar hanya membawa plpgsql — bahasa prosedural bawaan yang memang terpasang otomatis di setiap database. Empat extension lainnya tersedia namun belum aktif: kolom installed_version kosong sampai CREATE EXTENSION dijalankan. Perhatikan juga bahwa katalog ini per database: memasang pgcrypto di satu database tidak memasangnya di database lain, meskipun paketnya sudah ada untuk seluruh server.

CREATE EXTENSION, Versi, dan Pembaruan #

Bentuk paling sederhana memakai versi default dari control file. Untuk skrip migrasi yang bisa dijalankan ulang, tambahkan IF NOT EXISTS; untuk mengunci versi tertentu, sebutkan klausa VERSION. File berikut mempraktikkan ketiganya, lalu menutup dengan ALTER EXTENSION ... UPDATE yang menaikkan amcheck dari versi 1.3 ke 1.4 lewat update path:

-- Pemasangan standar: versi default dari control file.
CREATE EXTENSION pgcrypto;

-- IF NOT EXISTS menjadikan skrip aman dijalankan ulang.
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- Katalog pg_extension kini mencatat pgcrypto di database ini.
SELECT extname, extversion, extnamespace::regnamespace AS skema
FROM pg_extension
ORDER BY extname;

-- Mengunci versi lama secara eksplisit, lalu memperbaruinya.
CREATE EXTENSION amcheck VERSION '1.3';

SELECT extname, extversion
FROM pg_extension
WHERE extname = 'amcheck';

-- UPDATE mengikuti update path: 1.3 -> 1.4.
ALTER EXTENSION amcheck UPDATE;

SELECT extname, extversion
FROM pg_extension
WHERE extname = 'amcheck';

-- amcheck selesai dipakai untuk demonstrasi; dilepas kembali.
DROP EXTENSION amcheck;

Output yang Dihasilkan:

text
CREATE EXTENSION
CREATE EXTENSION
 extname  | extversion |   skema    
----------+------------+------------
 pgcrypto | 1.3        | public
 plpgsql  | 1.0        | pg_catalog
(2 rows)

CREATE EXTENSION
 extname | extversion 
---------+------------
 amcheck | 1.3
(1 row)

ALTER EXTENSION
 extname | extversion 
---------+------------
 amcheck | 1.4
(1 row)

DROP EXTENSION

Mari kita bedah perilaku versinya baris per baris:

  1. CREATE EXTENSION pgcrypto memasang versi 1.3 — nilai default_version dari control file yang kita baca di chapter sebelumnya. Katalog pg_extension mencatat pasangan nama dan versi ini.
  2. CREATE EXTENSION IF NOT EXISTS pgcrypto tidak mengubah apa pun; psql tetap mencetak tag CREATE EXTENSION, sementara NOTICE: extension "pgcrypto" already exists, skipping ditulis ke stderr sehingga tidak tampak di blok output. Inilah bentuk yang tepat untuk skrip migrasi.
  3. CREATE EXTENSION amcheck VERSION '1.3' memaksa versi lama secara eksplisit — pola yang berguna saat aplikasi belum kompatibel dengan versi terbaru.
  4. ALTER EXTENSION amcheck UPDATE mencari update path dari 1.3 menuju versi tertinggi (file amcheck--1.3--1.4.sql), menjalankannya, dan memperbarui katalog menjadi 1.4.

Menempatkan Extension di Schema Khusus #

Secara default objek extension jatuh ke schema public. Server produksi yang rapi sering memilih schema tersendiri — misalnya ekstensi — agar objek tambahan mudah diaudit, di-backup, dan tidak berbaur dengan tabel aplikasi. Klausa SCHEMA pada CREATE EXTENSION mengatur penempatan itu, dengan satu konsekuensi: nama objeknya kini harus memenuhi aturan search_path seperti objek schema lain:

sql skema.sql
-- Schema khusus menjaga objek extension tetap terkelompok dan mudah diaudit.
CREATE SCHEMA ekstensi;

-- Objek pg_trgm akan dibuat di dalam schema ekstensi, bukan public.
CREATE EXTENSION pg_trgm SCHEMA ekstensi;

SELECT extname, extversion, extnamespace::regnamespace AS skema
FROM pg_extension
WHERE extname IN ('pg_trgm', 'plpgsql')
ORDER BY extname;

-- Di luar search_path, pemanggilan harus menyebut nama skema penuh.
SELECT ekstensi.similarity('budi', 'budi santoso') AS skor_skema;

-- Masukkan schema ke search_path, lalu pemanggilan singkat kembali bekerja.
SET search_path = public, ekstensi;

SELECT similarity('budi', 'budi santoso') AS skor_search_path;

-- Kembalikan search_path ke nilai bawaan sesi.
RESET search_path;

-- Bersih-bersih: lepas extension, lalu schema kosongnya.
DROP EXTENSION pg_trgm;
DROP SCHEMA ekstensi;

Output yang Dihasilkan:

text
CREATE SCHEMA
CREATE EXTENSION
 extname | extversion |   skema    
---------+------------+------------
 pg_trgm | 1.6        | ekstensi
 plpgsql | 1.0        | pg_catalog
(2 rows)

 skor_skema 
------------
  0.3846154
(1 row)

SET
 skor_search_path 
------------------
        0.3846154
(1 row)

RESET
DROP EXTENSION
DROP SCHEMA

Katalog mengonfirmasi penempatan: pg_trgm kini bernaung di schema ekstensi, bukan public. Fungsi similarity() kemudian dipanggil dua cara — dengan prefiks ekstensi.similarity(...) yang selalu bekerja, dan tanpa prefiks yang hanya bekerja setelah SET search_path memasukkan schema ekstensi. Tanpa keduanya, pemanggilan singkat gagal dengan pesan function similarity(unknown, unknown) does not exist — bukan karena fungsinya hilang, melainkan karena PostgreSQL tidak mencarinya di schema itu. Extension relocatable = true (seperti pg_trgm) bahkan bisa dipindahkan setelah terpasang lewat ALTER EXTENSION ... SET SCHEMA.

Melepas Extension: RESTRICT dan CASCADE #

DROP EXTENSION adalah kebalikan pemasangan — dengan satu pintu pengaman penting. Secara default perintah berjalan mode RESTRICT: bila masih ada objek yang bergantung pada milik extension, pelepasan ditolak. Contoh berikut memasang citext, memakai tipenya sebagai kolom, lalu membuktikan penolakan itu:

-- citext dipasang kembali untuk contoh ketergantungan.
CREATE EXTENSION citext;

CREATE TABLE pelanggan_ci (
    id    integer PRIMARY KEY,
    email citext NOT NULL UNIQUE
);

INSERT INTO pelanggan_ci VALUES
    (1, '[email protected]'),
    (2, '[email protected]');

-- Pencocokan citext mengabaikan besar-kecil huruf.
SELECT id, email
FROM pelanggan_ci
WHERE email = '[email protected]'
ORDER BY id;

Output yang Dihasilkan:

text
CREATE EXTENSION
CREATE TABLE
INSERT 0 2
 id |      email       
----+------------------
  1 | [email protected]
(1 row)
sql lepas-gagal.sql
-- Kolom email bertipe citext masih bergantung pada extension ini,
-- sehingga pelepasan standar (RESTRICT) ditolak server.
DROP EXTENSION citext;

Output yang Dihasilkan:

text
psql:lepas-gagal.sql:3: ERROR:  cannot drop extension citext because other objects depend on it
DETAIL:  column email of table pelanggan_ci depends on type citext
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

Error tersebut adalah pengaman yang bekerja semestinya: kolom email akan kehilangan tipenya bila citext hilang, jadi PostgreSQL menolak dan menawarkan CASCADE lewat HINT. File berikut menempuh jalan itu:

-- CASCADE melepaskan dulu objek yang bergantung, lalu extension-nya.
-- Server mengirim NOTICE "drop cascades to column email of table pelanggan_ci".
DROP EXTENSION citext CASCADE;

-- Struktur setelah CASCADE: kolom email ikut hilang, tabel dan id tetap hidup.
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'pelanggan_ci'
ORDER BY ordinal_position;

-- Data pada kolom yang selamat tidak terganggu.
SELECT id
FROM pelanggan_ci
ORDER BY id;

Output yang Dihasilkan:

text
DROP EXTENSION
 column_name | data_type 
-------------+-----------
 id          | integer
(1 row)

 id 
----
  1
  2
(2 rows)

Perhatikan presisi perilaku CASCADE: PostgreSQL melepas objek bergantung seminimal mungkin — di sini kolom email beserta constraint UNIQUE-nya — sementara tabel pelanggan_ci dan kolom id tetap hidup lengkap dengan datanya. NOTICE yang menyebutkan objek yang dihapus tercetak ke stderr. Bila tipe extension dipakai banyak tabel, efeknya berlipat: setiap kolom yang bersangkutan lenyap, dan tabel yang kehilangan seluruh kolomnya ikut dihapus. Periksa pg_depend atau jalankan DROP EXTENSION tanpa CASCADE lebih dulu untuk melihat daftar objek yang akan terkena dampak.

Siklus hidup extension kini lengkap: tersedia, terpasang, terperbarui, terkelola schemanya, dan terlepas dengan aman. Ekosistem yang sesungguhnya dimulai dari extension pertama yang dipasang untuk alasan nyata — dan tidak ada kandidat yang lebih nyata daripada pg_stat_statements, profiler query standar de facto yang akan kita aktifkan di chapter berikutnya.

Referensi #

Berkas Sumber

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

cek-ketersediaan.sql

Unduh berkas
sql
-- Extension yang TERPASANG di database ini (katalog pg_extension).
\dx

-- Bentuk SQL-nya: katalog pg_extension berisi extension aktif.
SELECT extname, extversion, extnamespace::regnamespace AS skema
FROM pg_extension
ORDER BY extname;

-- Extension yang TERSEDIA namun belum terpasang: kolom installed_version NULL.
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE name IN ('pgcrypto', 'pg_trgm', 'citext', 'amcheck')
ORDER BY name;

error-paket-hilang.sql

Unduh berkas
sql
-- pgbuffercache tidak ada di pg_available_extensions server kursus
-- karena paketnya tidak ikut dipasang di sistem operasi.
CREATE EXTENSION pgbuffercache;

ketergantungan.sql

Unduh berkas
sql
-- citext dipasang kembali untuk contoh ketergantungan.
CREATE EXTENSION citext;

CREATE TABLE pelanggan_ci (
    id    integer PRIMARY KEY,
    email citext NOT NULL UNIQUE
);

INSERT INTO pelanggan_ci VALUES
    (1, '[email protected]'),
    (2, '[email protected]');

-- Pencocokan citext mengabaikan besar-kecil huruf.
SELECT id, email
FROM pelanggan_ci
WHERE email = '[email protected]'
ORDER BY id;

lepas-cascade.sql

Unduh berkas
sql
-- CASCADE melepaskan dulu objek yang bergantung, lalu extension-nya.
-- Server mengirim NOTICE "drop cascades to column email of table pelanggan_ci".
DROP EXTENSION citext CASCADE;

-- Struktur setelah CASCADE: kolom email ikut hilang, tabel dan id tetap hidup.
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'pelanggan_ci'
ORDER BY ordinal_position;

-- Data pada kolom yang selamat tidak terganggu.
SELECT id
FROM pelanggan_ci
ORDER BY id;

lepas-gagal.sql

Unduh berkas
sql
-- Kolom email bertipe citext masih bergantung pada extension ini,
-- sehingga pelepasan standar (RESTRICT) ditolak server.
DROP EXTENSION citext;

pasang-dan-versi.sql

Unduh berkas
sql
-- Pemasangan standar: versi default dari control file.
CREATE EXTENSION pgcrypto;

-- IF NOT EXISTS menjadikan skrip aman dijalankan ulang.
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- Katalog pg_extension kini mencatat pgcrypto di database ini.
SELECT extname, extversion, extnamespace::regnamespace AS skema
FROM pg_extension
ORDER BY extname;

-- Mengunci versi lama secara eksplisit, lalu memperbaruinya.
CREATE EXTENSION amcheck VERSION '1.3';

SELECT extname, extversion
FROM pg_extension
WHERE extname = 'amcheck';

-- UPDATE mengikuti update path: 1.3 -> 1.4.
ALTER EXTENSION amcheck UPDATE;

SELECT extname, extversion
FROM pg_extension
WHERE extname = 'amcheck';

-- amcheck selesai dipakai untuk demonstrasi; dilepas kembali.
DROP EXTENSION amcheck;

skema.sql

Unduh berkas
sql
-- Schema khusus menjaga objek extension tetap terkelompok dan mudah diaudit.
CREATE SCHEMA ekstensi;

-- Objek pg_trgm akan dibuat di dalam schema ekstensi, bukan public.
CREATE EXTENSION pg_trgm SCHEMA ekstensi;

SELECT extname, extversion, extnamespace::regnamespace AS skema
FROM pg_extension
WHERE extname IN ('pg_trgm', 'plpgsql')
ORDER BY extname;

-- Di luar search_path, pemanggilan harus menyebut nama skema penuh.
SELECT ekstensi.similarity('budi', 'budi santoso') AS skor_skema;

-- Masukkan schema ke search_path, lalu pemanggilan singkat kembali bekerja.
SET search_path = public, ekstensi;

SELECT similarity('budi', 'budi santoso') AS skor_search_path;

-- Kembalikan search_path ke nilai bawaan sesi.
RESET search_path;

-- Bersih-bersih: lepas extension, lalu schema kosongnya.
DROP EXTENSION pg_trgm;
DROP SCHEMA ekstensi;