Peta privilege dari chapter sebelumnya kini menemukan kata kerjanya. GRANT membuka akses dengan tingkat presisi yang bisa sampai kolom per kolom; REVOKE menutupnya kembali dengan dua mode pencabutan yang berkarakter sangat berbeda — RESTRICT yang hati-hati dan CASCADE yang menyapu grant turunan. Sistem ini juga mengenal delegasi: sebuah role yang menerima hak dengan WITH GRANT OPTION boleh membagikannya kembali tanpa melibatkan pemilik objek, dan rantai delegasi itulah yang membuat pencabutan menjadi topik yang menuntut pemahaman, bukan sekadar hafalan sintaks. Chapter ini merangkai seluruh mekanisme tersebut dalam satu alur demo yang sama-sama bisa Anda jalankan ulang.

GRANT Dasar dan Pembuktiannya #

Bentuk paling dasar dari pemberian hak adalah GRANT <privilege> ON <objek> TO <role>. File pembuka menyiapkan dua tabel dan tiga role, memberikan SELECT satu tabel kepada kasir_tmj dan ALL PRIVILEGES pada tabel lain kepada staf_tmj, lalu membuktikan keduanya dengan SET ROLE:

-- Bahan latihan GRANT dan REVOKE: dua tabel dan tiga role,
-- lalu pemberian hak paling dasar untuk dibuktikan lewat SET ROLE.
DROP ROLE IF EXISTS kasir_tmj;
DROP ROLE IF EXISTS admin_tmj;
DROP ROLE IF EXISTS staf_tmj;

CREATE ROLE kasir_tmj LOGIN;
CREATE ROLE admin_tmj LOGIN;
CREATE ROLE staf_tmj LOGIN;

CREATE TABLE penjualan (
    id     integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    produk text NOT NULL,
    jumlah integer NOT NULL DEFAULT 1,
    harga  numeric(10,2) NOT NULL DEFAULT 0
);

INSERT INTO penjualan (produk, jumlah, harga) VALUES
    ('Kopi Gayo',  10, 85000.00),
    ('Teh Melati', 25, 35000.00);

CREATE TABLE produk (
    id    integer PRIMARY KEY,
    nama  text NOT NULL,
    harga numeric(10,2) NOT NULL
);

INSERT INTO produk (id, nama, harga) VALUES
    (1, 'Kopi Gayo', 85000.00),
    (2, 'Teh Melati', 35000.00);

-- Grant paling dasar: hak baca satu tabel untuk satu role.
GRANT SELECT ON penjualan TO kasir_tmj;

SET ROLE kasir_tmj;

SELECT id, produk, jumlah, harga
FROM penjualan
ORDER BY id;

RESET ROLE;

-- GRANT ALL PRIVILEGES memberikan seluruh hak yang tersedia pada objek.
GRANT ALL PRIVILEGES ON produk TO staf_tmj;

SET ROLE staf_tmj;

SELECT id, nama, harga
FROM produk
ORDER BY id;

RESET ROLE;

Output yang Dihasilkan:

text
DROP ROLE
DROP ROLE
DROP ROLE
CREATE ROLE
CREATE ROLE
CREATE ROLE
CREATE TABLE
INSERT 0 2
CREATE TABLE
INSERT 0 2
GRANT
SET
 id |   produk   | jumlah |  harga   
----+------------+--------+----------
  1 | Kopi Gayo  |     10 | 85000.00
  2 | Teh Melati |     25 | 35000.00
(2 rows)

RESET
GRANT
SET
 id |    nama    |  harga   
----+------------+----------
  1 | Kopi Gayo  | 85000.00
  2 | Teh Melati | 35000.00
(2 rows)

RESET

Dua grant di file itu mewakili dua gaya pemberian hak. GRANT SELECT ON penjualan TO kasir_tmj memberikan satu hak secara eksplisit — gaya yang sebaiknya jadi bawaan Anda karena mudah diaudit. GRANT ALL PRIVILEGES ON produk TO staf_tmj memberikan sekaligus seluruh hak yang relevan untuk jenis objeknya — SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, plus MAINTAIN yang baru di PostgreSQL 17; praktis untuk objek internal tim, boros untuk objek yang menghadap aplikasi eksternal. Kedua SET ROLE di akhir tiap blok memastikan hak itu bukan klaim di atas kertas: kasir_tmj benar-benar membaca penjualan, dan staf_tmj membaca produk.

Hak yang Tidak Diberikan Tidak Bisa Dipakai #

Sisi pengingkaran sistem privilege sama pentingnya dengan sisi pemberiannya. kasir_tmj hanya memegang SELECT pada penjualan, sehingga upaya berikut ditolak keras:

-- kasir_tmj hanya memegang SELECT pada penjualan.
-- Mencoba menulis baris baru ditolak mentah-mentah.
SET ROLE kasir_tmj;

INSERT INTO penjualan (produk, jumlah, harga)
VALUES ('Gula Merah', 5, 15000.00);

Output yang Dihasilkan:

text
psql:tulis-ditolak.sql:6: ERROR:  permission denied for table penjualan

Pesan permission denied for table penjualan adalah perilaku yang benar dan justru diinginkan. Tidak ada mekanisme “menulis sebagian karena bisa membaca” — privilege SELECT dan INSERT berdiri sendiri-sendiri, dan aplikasi kasir yang hanya perlu membaca laporan akan gagal menulis apa pun sejak baris pertama. Desain seperti ini disebut fail closed: kesalahan konfigurasi membuat akses tertutup, bukan terbuka.

Grant Tingkat Kolom #

Presisi tertinggi GRANT pada tabel dicapai lewat daftar kolom dalam tanda kurung. Kasus penggunaannya nyata: aplikasi kasir boleh menambah catatan penjualan dan mengoreksi harga, tetapi tidak boleh memilih nomor nota sendiri ataupun mengganti nama produk yang sudah tercatat. Tabel latihan kita mendukung skenario itu — kolom id adalah identity column yang terisi otomatis:

sql grant-kolom.sql
-- Grant tingkat kolom: kasir boleh menulis kolom tertentu saja.
-- INSERT diizinkan untuk produk, jumlah, harga -- tetapi tidak untuk id;
-- UPDATE diizinkan hanya untuk harga.
GRANT INSERT (produk, jumlah, harga) ON penjualan TO kasir_tmj;
GRANT UPDATE (harga) ON penjualan TO kasir_tmj;

SET ROLE kasir_tmj;

-- INSERT berhasil: id terisi otomatis oleh identity column,
-- kolom yang tidak diberikan tidak disebut dalam daftar kolom.
INSERT INTO penjualan (produk, jumlah, harga)
VALUES ('Gula Merah', 5, 15000.00);

-- UPDATE berhasil pada kolom harga; klausa WHERE membaca kolom id
-- yang diizinkan oleh privilege SELECT tingkat tabel.
UPDATE penjualan SET harga = 90000.00 WHERE id = 1;

RESET ROLE;

SELECT id, produk, jumlah, harga
FROM penjualan
ORDER BY id;

-- \dp kini menampilkan dua seksi sekaligus: hak tabel (kasir_tmj=r)
-- dan hak kolom per kolom yang diberikan.
\dp penjualan

-- Catalog information_schema.column_privileges merinci hak kolom
-- dalam bentuk yang bisa ditanyai seperti tabel biasa.
SELECT grantee, column_name, privilege_type
FROM information_schema.column_privileges
WHERE table_name = 'penjualan'
  AND grantee = 'kasir_tmj'
ORDER BY column_name, privilege_type;

Output yang Dihasilkan:

text
GRANT
GRANT
SET
INSERT 0 1
UPDATE 1
RESET
 id |   produk   | jumlah |  harga   
----+------------+--------+----------
  1 | Kopi Gayo  |     10 | 90000.00
  2 | Teh Melati |     25 | 35000.00
  3 | Gula Merah |      5 | 15000.00
(3 rows)

                                      Access privileges
 Schema |   Name    | Type  |     Access privileges      |    Column privileges    | Policies 
--------+-----------+-------+----------------------------+-------------------------+----------
 public | penjualan | table | postgres=arwdDxtm/postgres+| produk:                +| 
        |           |       | kasir_tmj=r/postgres       |   kasir_tmj=a/postgres +| 
        |           |       |                            | jumlah:                +| 
        |           |       |                            |   kasir_tmj=a/postgres +| 
        |           |       |                            | harga:                 +| 
        |           |       |                            |   kasir_tmj=aw/postgres | 
(1 row)

  grantee  | column_name | privilege_type 
-----------+-------------+----------------
 kasir_tmj | harga       | INSERT
 kasir_tmj | harga       | SELECT
 kasir_tmj | harga       | UPDATE
 kasir_tmj | id          | SELECT
 kasir_tmj | jumlah      | INSERT
 kasir_tmj | jumlah      | SELECT
 kasir_tmj | produk      | INSERT
 kasir_tmj | produk      | SELECT
(8 rows)

Mari kita bedah hasilnya baris demi baris:

  1. GRANT INSERT (produk, jumlah, harga) ... — kasir_tmj boleh menulis tiga kolom itu saja; kolom id tidak termasuk sehingga nomor nota tetap urusan identity column.
  2. INSERT INTO penjualan (produk, jumlah, harga) VALUES (...) — berhasil tanpa menyebut id; INSERT 0 1 mengonfirmasi satu baris baru bernomor 3.
  3. UPDATE penjualan SET harga = ... WHERE id = 1 — berhasil karena harga masuk daftar UPDATE, sementara pembacaan id di klausa WHERE ditutupi privilege SELECT tingkat tabel dari contoh pertama.
  4. Seksi Column privileges pada \dp — kini terisi: produk dan jumlah memuat kasir_tmj=a (INSERT), harga memuat kasir_tmj=aw (INSERT + UPDATE).
  5. Query information_schema.column_privileges — delapan baris rincian: hak INSERT pada tiga kolom, UPDATE pada harga, plus SELECT yang tersirat dari grant tingkat tabel.

Balikannya juga teruji — kolom yang tidak diberikan tetap terkunci meski barisnya terlihat:

-- Kolom yang tidak diberikan tetap terkunci: kasir boleh memperbarui
-- harga saja, dan mencoba mengubah nama produk ditolak.
SET ROLE kasir_tmj;

UPDATE penjualan SET produk = 'Teh Hijau' WHERE id = 2;

Output yang Dihasilkan:

text
psql:kolom-lain-ditolak.sql:5: ERROR:  permission denied for table penjualan

kasir_tmj berhasil membaca baris id 2 lewat SELECT, tetapi UPDATE penjualan SET produk = ... ditolak dengan pesan yang sama seperti penolakan tingkat tabel. Perhatikan detail pentingnya: privilege tingkat kolom untuk UPDATE tidak otomatis menyertakan hak membaca kolom lain — klausa WHERE pada contoh berhasil semata-mata karena grant SELECT tingkat tabel sudah berlaku.

WITH GRANT OPTION dan Rantai Delegasi #

Sampai titik ini semua grant berasal dari pemilik objek. WITH GRANT OPTION mengubah arsitektur itu: penerima hak sekaligus berhak membagikannya kepada role lain, lengkap dengan catatan grantor yang menjadikan rantai delegasi bisa dilacak. File berikut juga memperkenalkan pseudo-role PUBLIC — penerima yang mewakili semua role sekaligus:

-- WITH GRANT OPTION: admin boleh membagikan lagi hak yang dipegangnya,
-- dan pseudo-role PUBLIC memberi hak ke semua role sekaligus.
GRANT SELECT ON penjualan TO admin_tmj WITH GRANT OPTION;

-- Sebagai admin: membagikan SELECT kepada staf tanpa peran postgres.
SET ROLE admin_tmj;

GRANT SELECT ON penjualan TO staf_tmj;

RESET ROLE;

-- staf kini bisa membaca penjualan lewat grant dari admin.
SET ROLE staf_tmj;

SELECT count(*) AS total_baris FROM penjualan;

RESET ROLE;

-- GRANT ke PUBLIC berlaku untuk setiap role di cluster,
-- termasuk kasir yang tidak pernah menerima grant pada produk.
GRANT SELECT ON produk TO PUBLIC;

SET ROLE kasir_tmj;

SELECT id, nama
FROM produk
ORDER BY id;

RESET ROLE;

Output yang Dihasilkan:

text
GRANT
SET
GRANT
RESET
SET
 total_baris 
-------------
           3
(1 row)

RESET
GRANT
SET
 id |    nama    
----+------------
  1 | Kopi Gayo
  2 | Teh Melati
(2 rows)

RESET

Alurnya tiga lapis. Pertama, postgres memberi admin_tmj hak SELECT disertai WITH GRANT OPTION. Kedua, dari balik SET ROLE admin_tmj, sang admin menjalankan GRANT SELECT ON penjualan TO staf_tmj — sah karena opsi delegasinya, dan staf_tmj terbukti membaca tiga baris. Ketiga, GRANT SELECT ON produk TO PUBLIC membuka tabel produk untuk semua role; kasir_tmj yang tidak pernah menerima grant khusus pada tabel itu kini bisa membacanya. Pola PUBLIC ini tepat untuk data referensi umum, dan berbahaya bila dipakai untuk data transaksi.

REVOKE: RESTRICT, CASCADE, dan Grant Turunan #

Mencabut hak yang pernah didelegasikan punya dua filosofi. RESTRICT — mode bawaan — menolak mencabut bila ada grant turunan yang akan ikut hilang tanpa persetujuan; CASCADE memaksa pencabutan total beserta seluruh rantai turunannya. Karena admin_tmj masih memegang grant dari staf_tmj, mode pertama ini digagalkan:

-- REVOKE ... RESTRICT adalah mode bawaan: menolak mencabut hak
-- bila ada grant turunan yang bergantung padanya.
-- admin_tmj masih membagikan SELECT kepada staf_tmj,
-- sehingga pencabutan ini digagalkan sebelum berdampak.
REVOKE SELECT ON penjualan FROM admin_tmj RESTRICT;

Output yang Dihasilkan:

text
psql:revoke-restriktif.sql:5: ERROR:  dependent privileges exist
HINT:  Use CASCADE to revoke them too.

Error dependent privileges exist disertai HINT: Use CASCADE to revoke them too — PostgreSQL bukan menolak mencabut, melainkan meminta keputusan eksplisit tentang nasib grant turunan. Keputusan itu diambil di file berikut:

-- REVOKE ... CASCADE memaksa pencabutan beserta seluruh grant turunannya.
REVOKE SELECT ON penjualan FROM admin_tmj CASCADE;

-- admin kehilangan SELECT-nya, dan staf ikut kehilangan grant
-- yang diterimanya dari admin (sumber satu-satunya bagi staf).
SELECT has_table_privilege('admin_tmj', 'penjualan', 'SELECT') AS admin_baca,
       has_table_privilege('staf_tmj',  'penjualan', 'SELECT') AS staf_baca;

-- \dp memastikan hanya kasir_tmj yang masih memegang hak pada penjualan
-- (beserta hak kolom dari contoh sebelumnya).
\dp penjualan

Output yang Dihasilkan:

text
REVOKE
 admin_baca | staf_baca 
------------+-----------
 f          | f
(1 row)

                                      Access privileges
 Schema |   Name    | Type  |     Access privileges      |    Column privileges    | Policies 
--------+-----------+-------+----------------------------+-------------------------+----------
 public | penjualan | table | postgres=arwdDxtm/postgres+| produk:                +| 
        |           |       | kasir_tmj=r/postgres       |   kasir_tmj=a/postgres +| 
        |           |       |                            | jumlah:                +| 
        |           |       |                            |   kasir_tmj=a/postgres +| 
        |           |       |                            | harga:                 +| 
        |           |       |                            |   kasir_tmj=aw/postgres | 
(1 row)

Hasilnya tegas pada dua kolom boolean: admin_baca bernilai f dan staf_baca bernilai f. Grant milik admin dicabut, dan grant staf_tmj ikut lenyap karena bersumber darinya — bila staf_tmj juga punya grant langsung dari postgres, hak itu akan bertahan dan kolomnya tetap t. Tampilan \dp menutup pembuktian: pada penjualan tinggal kasir_tmj=r/postgres beserta hak kolomnya dari contoh sebelumnya. Inilah yang dimaksud cascade revocation — pencabutan menjalar menuruni pohon delegasi.

Mencabut Opsi Delegasi Tanpa Mencabut Haknya #

REVOKE punya satu presisi terakhir: REVOKE GRANT OPTION FOR ... mencabut hak membagikan saja dan membiarkan hak dasarnya melekat. File terakhir merangkai variasi ini sekaligus membersihkan seluruh role latihan:

-- REVOKE GRANT OPTION FOR: mencabut hak membagikan saja,
-- tanpa mencabut hak dasarnya.
GRANT SELECT ON penjualan TO admin_tmj WITH GRANT OPTION;

SET ROLE admin_tmj;

GRANT SELECT ON penjualan TO staf_tmj;

RESET ROLE;

-- Opsi GRANT dicabut dengan CASCADE: grant turunan staf ikut hilang,
-- tetapi hak SELECT milik admin sendiri tetap melekat.
REVOKE GRANT OPTION FOR SELECT ON penjualan FROM admin_tmj CASCADE;

SELECT has_table_privilege('admin_tmj', 'penjualan', 'SELECT') AS admin_baca,
       has_table_privilege('staf_tmj',  'penjualan', 'SELECT') AS staf_baca;

-- Bukti hak dasar admin bertahan: SELECT tetap berhasil.
SET ROLE admin_tmj;

SELECT count(*) AS total_baris FROM penjualan;

RESET ROLE;

-- Pembersihan tuntas seluruh role latihan chapter ini.
DROP OWNED BY kasir_tmj, admin_tmj, staf_tmj;

DROP ROLE kasir_tmj;
DROP ROLE admin_tmj;
DROP ROLE staf_tmj;

SELECT count(*) AS sisa_role_latihan
FROM pg_roles
WHERE rolname IN ('kasir_tmj', 'admin_tmj', 'staf_tmj');

Output yang Dihasilkan:

text
GRANT
SET
GRANT
RESET
REVOKE
 admin_baca | staf_baca 
------------+-----------
 t          | f
(1 row)

SET
 total_baris 
-------------
           3
(1 row)

RESET
DROP OWNED
DROP ROLE
DROP ROLE
DROP ROLE
 sisa_role_latihan 
-------------------
                 0
(1 row)

Kontras dua kolomnya informatif: admin_baca tetap t sementara staf_baca jatuh ke f. Hak SELECT milik admin selamat — ditambah bukti SELECT count(*) yang masih mengembalikan tiga baris dari balik SET ROLE admin_tmj — tetapi grant turunan staf tersapu CASCADE karena opsi yang menjadi asal-usulnya sudah dicabut. Untuk penulisan ulang file latihan, blok penutup memakai pola kebersihan lama: DROP OWNED BY untuk ketiga role, DROP ROLE untuk masing-masing, dan verifikasi 0 sisa di pg_roles.

Dengan GRANT dan REVOKE di tangan, kita sudah bisa membuka dan menutup akses pada objek yang sudah ada. Namun praktik nyata menuntut satu hal lagi: tabel baru dibuat setiap hari, dan tidak realistis memberi GRANT ulang untuk setiap tabel baru. Jawabannya berada satu level di atas tabel — hak pada schema dan default privileges — yang menjadi bahasan chapter Schema Privileges .

Referensi #

Berkas Sumber

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

grant-kolom.sql

Unduh berkas
sql
-- Grant tingkat kolom: kasir boleh menulis kolom tertentu saja.
-- INSERT diizinkan untuk produk, jumlah, harga -- tetapi tidak untuk id;
-- UPDATE diizinkan hanya untuk harga.
GRANT INSERT (produk, jumlah, harga) ON penjualan TO kasir_tmj;
GRANT UPDATE (harga) ON penjualan TO kasir_tmj;

SET ROLE kasir_tmj;

-- INSERT berhasil: id terisi otomatis oleh identity column,
-- kolom yang tidak diberikan tidak disebut dalam daftar kolom.
INSERT INTO penjualan (produk, jumlah, harga)
VALUES ('Gula Merah', 5, 15000.00);

-- UPDATE berhasil pada kolom harga; klausa WHERE membaca kolom id
-- yang diizinkan oleh privilege SELECT tingkat tabel.
UPDATE penjualan SET harga = 90000.00 WHERE id = 1;

RESET ROLE;

SELECT id, produk, jumlah, harga
FROM penjualan
ORDER BY id;

-- \dp kini menampilkan dua seksi sekaligus: hak tabel (kasir_tmj=r)
-- dan hak kolom per kolom yang diberikan.
\dp penjualan

-- Catalog information_schema.column_privileges merinci hak kolom
-- dalam bentuk yang bisa ditanyai seperti tabel biasa.
SELECT grantee, column_name, privilege_type
FROM information_schema.column_privileges
WHERE table_name = 'penjualan'
  AND grantee = 'kasir_tmj'
ORDER BY column_name, privilege_type;

grant-option-cabut.sql

Unduh berkas
sql
-- REVOKE GRANT OPTION FOR: mencabut hak membagikan saja,
-- tanpa mencabut hak dasarnya.
GRANT SELECT ON penjualan TO admin_tmj WITH GRANT OPTION;

SET ROLE admin_tmj;

GRANT SELECT ON penjualan TO staf_tmj;

RESET ROLE;

-- Opsi GRANT dicabut dengan CASCADE: grant turunan staf ikut hilang,
-- tetapi hak SELECT milik admin sendiri tetap melekat.
REVOKE GRANT OPTION FOR SELECT ON penjualan FROM admin_tmj CASCADE;

SELECT has_table_privilege('admin_tmj', 'penjualan', 'SELECT') AS admin_baca,
       has_table_privilege('staf_tmj',  'penjualan', 'SELECT') AS staf_baca;

-- Bukti hak dasar admin bertahan: SELECT tetap berhasil.
SET ROLE admin_tmj;

SELECT count(*) AS total_baris FROM penjualan;

RESET ROLE;

-- Pembersihan tuntas seluruh role latihan chapter ini.
DROP OWNED BY kasir_tmj, admin_tmj, staf_tmj;

DROP ROLE kasir_tmj;
DROP ROLE admin_tmj;
DROP ROLE staf_tmj;

SELECT count(*) AS sisa_role_latihan
FROM pg_roles
WHERE rolname IN ('kasir_tmj', 'admin_tmj', 'staf_tmj');

grant-option.sql

Unduh berkas
sql
-- WITH GRANT OPTION: admin boleh membagikan lagi hak yang dipegangnya,
-- dan pseudo-role PUBLIC memberi hak ke semua role sekaligus.
GRANT SELECT ON penjualan TO admin_tmj WITH GRANT OPTION;

-- Sebagai admin: membagikan SELECT kepada staf tanpa peran postgres.
SET ROLE admin_tmj;

GRANT SELECT ON penjualan TO staf_tmj;

RESET ROLE;

-- staf kini bisa membaca penjualan lewat grant dari admin.
SET ROLE staf_tmj;

SELECT count(*) AS total_baris FROM penjualan;

RESET ROLE;

-- GRANT ke PUBLIC berlaku untuk setiap role di cluster,
-- termasuk kasir yang tidak pernah menerima grant pada produk.
GRANT SELECT ON produk TO PUBLIC;

SET ROLE kasir_tmj;

SELECT id, nama
FROM produk
ORDER BY id;

RESET ROLE;

kolom-lain-ditolak.sql

Unduh berkas
sql
-- Kolom yang tidak diberikan tetap terkunci: kasir boleh memperbarui
-- harga saja, dan mencoba mengubah nama produk ditolak.
SET ROLE kasir_tmj;

UPDATE penjualan SET produk = 'Teh Hijau' WHERE id = 2;

revoke-cascade.sql

Unduh berkas
sql
-- REVOKE ... CASCADE memaksa pencabutan beserta seluruh grant turunannya.
REVOKE SELECT ON penjualan FROM admin_tmj CASCADE;

-- admin kehilangan SELECT-nya, dan staf ikut kehilangan grant
-- yang diterimanya dari admin (sumber satu-satunya bagi staf).
SELECT has_table_privilege('admin_tmj', 'penjualan', 'SELECT') AS admin_baca,
       has_table_privilege('staf_tmj',  'penjualan', 'SELECT') AS staf_baca;

-- \dp memastikan hanya kasir_tmj yang masih memegang hak pada penjualan
-- (beserta hak kolom dari contoh sebelumnya).
\dp penjualan

revoke-restriktif.sql

Unduh berkas
sql
-- REVOKE ... RESTRICT adalah mode bawaan: menolak mencabut hak
-- bila ada grant turunan yang bergantung padanya.
-- admin_tmj masih membagikan SELECT kepada staf_tmj,
-- sehingga pencabutan ini digagalkan sebelum berdampak.
REVOKE SELECT ON penjualan FROM admin_tmj RESTRICT;

siapkan-grant.sql

Unduh berkas
sql
-- Bahan latihan GRANT dan REVOKE: dua tabel dan tiga role,
-- lalu pemberian hak paling dasar untuk dibuktikan lewat SET ROLE.
DROP ROLE IF EXISTS kasir_tmj;
DROP ROLE IF EXISTS admin_tmj;
DROP ROLE IF EXISTS staf_tmj;

CREATE ROLE kasir_tmj LOGIN;
CREATE ROLE admin_tmj LOGIN;
CREATE ROLE staf_tmj LOGIN;

CREATE TABLE penjualan (
    id     integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    produk text NOT NULL,
    jumlah integer NOT NULL DEFAULT 1,
    harga  numeric(10,2) NOT NULL DEFAULT 0
);

INSERT INTO penjualan (produk, jumlah, harga) VALUES
    ('Kopi Gayo',  10, 85000.00),
    ('Teh Melati', 25, 35000.00);

CREATE TABLE produk (
    id    integer PRIMARY KEY,
    nama  text NOT NULL,
    harga numeric(10,2) NOT NULL
);

INSERT INTO produk (id, nama, harga) VALUES
    (1, 'Kopi Gayo', 85000.00),
    (2, 'Teh Melati', 35000.00);

-- Grant paling dasar: hak baca satu tabel untuk satu role.
GRANT SELECT ON penjualan TO kasir_tmj;

SET ROLE kasir_tmj;

SELECT id, produk, jumlah, harga
FROM penjualan
ORDER BY id;

RESET ROLE;

-- GRANT ALL PRIVILEGES memberikan seluruh hak yang tersedia pada objek.
GRANT ALL PRIVILEGES ON produk TO staf_tmj;

SET ROLE staf_tmj;

SELECT id, nama, harga
FROM produk
ORDER BY id;

RESET ROLE;

tulis-ditolak.sql

Unduh berkas
sql
-- kasir_tmj hanya memegang SELECT pada penjualan.
-- Mencoba menulis baris baru ditolak mentah-mentah.
SET ROLE kasir_tmj;

INSERT INTO penjualan (produk, jumlah, harga)
VALUES ('Gula Merah', 5, 15000.00);