CHECK (harga >= 0), UNIQUE, foreign key — constraint di skema adalah janji bahwa data kotor tidak akan pernah masuk. Tetapi janji yang tidak pernah diuji hanyalah rumor: batas bawah yang salah tulis (> 0 alih-alih >= 0) membuat barang gratis tidak bisa disimpan, dan foreign key yang lupa dipasang membiarkan pesanan menunjuk barang yang tidak ada. Chapter ini membuktikan setiap constraint dengan tiga jenis uji: uji positif (data sah diterima), uji negatif (data tidak sah ditolak), dan uji batas (nilai tepat di tepi rentang sah). Termasuk satu pertanyaan yang sering muncul saat pemuatan massal: apakah COPY benar-benar menjalankan constraint yang sama?

Menyiapkan Tabel dengan Semua Jenis Constraint #

Fixture berikut menumpuk lima jenis constraint dalam dua tabel: PRIMARY KEY (implisit unique dan not-null), UNIQUE, NOT NULL, CHECK dengan rentang bawah dan atas, serta FOREIGN KEY. Rentang harga yang diuji adalah 0 sampai 10000000 — dua tepi yang akan digoncang dari kedua sisi:

-- Fixture pengujian integritas: satu tabel memuat semua jenis constraint
-- yang akan diuji: PRIMARY KEY, UNIQUE, NOT NULL, CHECK, dan FOREIGN KEY.
CREATE TABLE kategori (
    id   integer PRIMARY KEY,
    nama text NOT NULL UNIQUE
);

CREATE TABLE barang (
    kode        text PRIMARY KEY,
    nama        text NOT NULL,
    harga       integer NOT NULL CHECK (harga >= 0 AND harga <= 10000000),
    kategori_id integer NOT NULL REFERENCES kategori (id)
);

-- Nilai batas constraint CHECK harga: 0 dan 10000000 sama-sama sah.
INSERT INTO kategori VALUES
    (1, 'makanan'),
    (2, 'minuman'),
    (3, 'aksesoris');

Output yang Dihasilkan:

text
CREATE TABLE
CREATE TABLE
INSERT 0 3

Uji Positif: Data Sah Harus Diterima #

Uji positif memasukkan data yang sah — termasuk kedua nilai batas — lalu memverifikasi bahwa semuanya tersimpan. Jenis uji ini menangkap kesalahan “constraint terlalu ketat”: CHECK (harga > 0) yang menolak harga 0, atau unique yang ternyata sensitif huruf besar-kecil padahal tidak diinginkan:

sql uji-positif.sql
-- Uji positif: data sah (termasuk nilai batas) harus DITERIMA.
-- Sebuah constraint yang salah tulis bisa menolak data yang seharusnya sah.
INSERT INTO barang (kode, nama, harga, kategori_id) VALUES
    ('BRG-001', 'Roti Tawar',   15000, 1),
    ('BRG-002', 'Kantong Belanja',    0, 3),  -- batas bawah CHECK
    ('BRG-003', 'Mesin Kopi', 10000000, 3),  -- batas atas CHECK
    ('BRG-004', 'Teh Kotak',     6000, 2);

SELECT CASE
         WHEN (SELECT count(*) FROM barang) = 4
         THEN 'LULUS: 4 baris sah tersimpan, termasuk batas bawah 0 dan batas atas 10000000'
         ELSE 'GAGAL: ekspektasi 4, aktual ' || (SELECT count(*) FROM barang)::text
       END AS uji_positif;

Output yang Dihasilkan:

text
INSERT 0 4
                                 uji_positif                                  
------------------------------------------------------------------------------
 LULUS: 4 baris sah tersimpan, termasuk batas bawah 0 dan batas atas 10000000
(1 row)

Dua baris pertama dan keempat adalah data normal. Baris Kantong Belanja dengan harga 0 menguji tepi bawah, dan Mesin Kopi dengan harga 10000000 menguji tepi atas — keduanya harus diterima karena rentang CHECK bersifat inklusif (>= dan <=).

Uji Negatif Mentah: Error yang Diharapkan #

Bentuk paling sederhana dari uji negatif adalah file SQL yang berisikan satu perintah yang harus gagal:

sql uji-negatif.sql
-- Uji negatif bentuk paling sederhana: satu INSERT yang harus DITOLAK.
-- File ini sengaja berakhir dengan error — psql keluar dengan kode 3.
INSERT INTO barang (kode, nama, harga, kategori_id)
VALUES ('BRG-X1', 'Barang Uji', -1, 1);

Output yang Dihasilkan:

text
psql:uji-negatif.sql:4: ERROR:  new row for relation "barang" violates check constraint "barang_harga_check"
DETAIL:  Failing row contains (BRG-X1, Barang Uji, -1, 1).

Karena psql dijalankan dengan ON_ERROR_STOP=1, error ini menghentikan file dan membuat psql keluar dengan kode 3 — kode khusus “error SQL” yang membedakannya dari kegagal koneksi (kode 2). Pada pipeline integrasi berkelanjutan, kode keluar inilah yang dibaca mesin: exit 3 pada file yang memang menguji penolakan berarti lulus. Catatan pentingnya: error check_violation selalu membawa baris DETAIL berisi seluruh isi baris yang ditolak — informasi pertama yang Anda baca saat memburu data apa yang mencoba masuk.

Matriks Uji dengan DO Block #

Satu file exit 3 hanya menampung satu error — ON_ERROR_STOP menghentikan eksekusi di kegagalan pertama. Untuk menguji banyak kasus penolakan sekaligus, pola yang lebih kuat adalah menangkap error di dalam DO block: tiap INSERT dibungkus BEGIN ... EXCEPTION, kode SQLSTATE-nya direkam, lalu dibandingkan dengan harapan. File berikut menguji lima kasus dalam satu eksekusi:

sql uji-matriks.sql
-- Matriks uji negatif: banyak kasus penolakan dalam SATU file.
-- DO block menangkap error tiap INSERT, mencatat SQLSTATE-nya,
-- lalu membandingkan dengan SQLSTATE yang diantisipasi.
CREATE TABLE hasil_uji (
    kasus          text,
    sqlstate_aktual text,
    sqlstate_harapan text
);

DO $$
DECLARE
    v_kasus  text;
    v_kode   text;
BEGIN
    FOREACH v_kasus IN ARRAY ARRAY[
        'harga di bawah batas (-1)',
        'harga di atas batas (10000001)',
        'nama NULL',
        'kode duplikat',
        'kategori tidak ada (FK yatim)'
    ] LOOP
        BEGIN
            IF v_kasus = 'harga di bawah batas (-1)' THEN
                INSERT INTO barang VALUES ('UJI-01', 'Uji A', -1, 1);
            ELSIF v_kasus = 'harga di atas batas (10000001)' THEN
                INSERT INTO barang VALUES ('UJI-02', 'Uji B', 10000001, 1);
            ELSIF v_kasus = 'nama NULL' THEN
                INSERT INTO barang VALUES ('UJI-03', NULL, 5000, 1);
            ELSIF v_kasus = 'kode duplikat' THEN
                INSERT INTO barang VALUES ('BRG-001', 'Duplikat', 5000, 1);
            ELSE
                INSERT INTO barang VALUES ('UJI-04', 'Uji C', 5000, 99);
            END IF;
            -- Baris ini hanya tercapai bila INSERT lolos — itu kegagalan uji.
            INSERT INTO hasil_uji VALUES (v_kasus, 'TIDAK DITOLAK', '23514');
        EXCEPTION WHEN OTHERS THEN
            GET STACKED DIAGNOSTICS v_kode = RETURNED_SQLSTATE;
            INSERT INTO hasil_uji VALUES (v_kasus, v_kode, NULL);
        END;
    END LOOP;
END $$;

UPDATE hasil_uji SET sqlstate_harapan = CASE
    WHEN kasus LIKE 'harga%'        THEN '23514'  -- check_violation
    WHEN kasus = 'nama NULL'        THEN '23502'  -- not_null_violation
    WHEN kasus = 'kode duplikat'    THEN '23505'  -- unique_violation
    ELSE '23503'                                 -- foreign_key_violation
END;

SELECT kasus,
       sqlstate_aktual,
       CASE WHEN sqlstate_aktual = sqlstate_harapan
            THEN 'LULUS'
            ELSE 'GAGAL: harapan ' || sqlstate_harapan || ', aktual ' || sqlstate_aktual
       END AS status
FROM hasil_uji
ORDER BY kasus;

-- Bukti akhir: tidak ada satu pun baris uji yang bocor masuk.
SELECT CASE WHEN (SELECT count(*) FROM barang WHERE kode LIKE 'UJI-%') = 0
            THEN 'LULUS: tidak ada baris tidak sah yang tersimpan'
            ELSE 'GAGAL: ada baris tidak sah yang tersimpan'
       END AS uji_kebocoran;

Output yang Dihasilkan:

text
CREATE TABLE
DO
UPDATE 5
             kasus              | sqlstate_aktual | status 
--------------------------------+-----------------+--------
 harga di atas batas (10000001) | 23514           | LULUS
 harga di bawah batas (-1)      | 23514           | LULUS
 kategori tidak ada (FK yatim)  | 23503           | LULUS
 kode duplikat                  | 23505           | LULUS
 nama NULL                      | 23502           | LULUS
(5 rows)

                  uji_kebocoran                  
-------------------------------------------------
 LULUS: tidak ada baris tidak sah yang tersimpan
(1 row)

Matriks lengkap yang diuji file tersebut:

ConstraintKasus tidak sahSQLSTATE harapan
CHECKharga -1 (di bawah batas)23514 check_violation
CHECKharga 10000001 (di atas batas)23514 check_violation
NOT NULLkolom nama kosong23502 not_null_violation
PRIMARY KEYkode duplikat BRG-00123505 unique_violation
FOREIGN KEYkategori_id 99 tidak ada23503 foreign_key_violation

Bedah mekanismenya:

  1. Cabang IF per kasus — setiap kasus menjalankan INSERT penyerangannya sendiri. Bila INSERT ternyata lolos, baris hasil dicatat sebagai 'TIDAK DITOLAK' yang otomatis gagal saat dibandingkan — constraint yang bocor tertangkap.
  2. EXCEPTION WHEN OTHERS + GET STACKED DIAGNOSTICS — menangkap error apa pun dan mengambil RETURNED_SQLSTATE. Pembandingan dengan harapan tetap eksplisit di luar DO block, sehingga membaca file uji seperti membaca spesifikasi.
  3. Asersi kebocoran terakhir — count(*) WHERE kode LIKE 'UJI-%' memastikan tidak ada baris uji yang tersimpan. Constraint yang menolak dengan error tetap bisa saja “menolak” sambil menyimpan setengah baris — asersi ini menutup celah tersebut.

Pola DO block ini menjadi favorit karena laporan lengkap terlihat dalam satu output, tanpa perlu lima file terpisah. Teknik penangkapan errornya sendiri sudah dibahas tuntas di chapter Exception Handling .

Constraint Tetap Berlaku Saat COPY #

Mitos yang beredar: “batch import bisa melewati constraint”. Faktanya, COPY menjalankan pemeriksaan yang persis sama dengan INSERT — dan lebih dari itu, COPY bersifat atomik: satu baris buruk menggulung balik seluruh batch, bukan hanya barisnya. Skrip berikut membuktikan keduanya dengan dua batch:

#!/usr/bin/env bash
# Pengujian integritas saat pemuatan massal: COPY menjalankan constraint
# yang sama seperti INSERT, dan satu baris buruk menggulung balik seluruh batch.
echo '==> batch 1: tiga baris sah lewat COPY'
psql -X -q <<'SQL' 2>&1
COPY barang (kode, nama, harga, kategori_id) FROM STDIN WITH (FORMAT csv);
BRG-006,Gula Pasir,14000,2
BRG-007,Teh Celup,8000,2
BRG-008,Susu Kental,18000,2
\.
SELECT count(*) AS jumlah_barang FROM barang;
SQL

echo '==> batch 2: satu baris harga negatif tersembunyi di antara baris sah'
if printf 'BRG-009,Kopi Bubuk,25000,2\nBRG-010,Keju Slice,-45000,2\nBRG-011,Madu Hutan,32000,2\n\\.\n' \
   | psql -X -c "COPY barang (kode, nama, harga, kategori_id) FROM STDIN WITH (FORMAT csv)" 2>&1; then
  echo 'batch 2 DITERIMA (tidak sesuai harapan)'
else
  echo 'batch 2 DITOLAK'
fi

psql -X -t -A -c "SELECT 'jumlah barang tetap: ' || count(*) || ' — tidak ada baris batch 2 yang bocor masuk' AS kesimpulan FROM barang;"

Output yang Dihasilkan:

text
==> batch 1: tiga baris sah lewat COPY
 jumlah_barang 
---------------
             7
(1 row)

==> batch 2: satu baris harga negatif tersembunyi di antara baris sah
ERROR:  new row for relation "barang" violates check constraint "barang_harga_check"
DETAIL:  Failing row contains (BRG-010, Keju Slice, -45000, 2).
CONTEXT:  COPY barang, line 2: "BRG-010,Keju Slice,-45000,2"
batch 2 DITOLAK
jumlah barang tetap: 7 — tidak ada baris batch 2 yang bocor masuk

Batch pertama memuat tiga baris sah — jumlah barang naik dari 4 ke 7. Batch kedua menyembunyikan satu harga negatif (Keju Slice) di antara dua baris sah: error check_violation muncul dengan CONTEXT yang menyebut baris CSV penyebabnya, dan jumlah barang tetap 7 — bukan 8 atau 9. Tidak ada baris yang bocor masuk; pemuatan massal tidak bisa dipakai sebagai pintu belakang untuk data kotor.

Audit Integritas Sebelum Memasang FK #

Skenario paling umum di sistem warisan: tabel lama dibuat tanpa foreign key, bertahun-tahun menampung data, dan kini berisi pesanan yang menunjuk barang tidak dikenal (orphan). Memasang FK langsung pada data seperti itu akan gagal — PostgreSQL menolak membuat constraint yang tidak terpenuhi. Urutan yang benar: audit dulu, bersihkan, baru pasang:

-- Skenario warisan sistem lama: tabel pesanan_lama dibuat TANPA foreign key
-- dan sudah berisi data termasuk kode barang yang tidak ada di tabel barang.
CREATE TABLE pesanan_lama (
    id         integer PRIMARY KEY,
    kode_barang text NOT NULL,
    jumlah     integer NOT NULL
);

INSERT INTO pesanan_lama VALUES
    (1, 'BRG-001', 2),
    (2, 'BRG-777', 1),   -- yatim: kode tidak ada di tabel barang
    (3, 'BRG-004', 5);

-- Audit integritas referensial: cari pesanan yang menunjuk barang tidak dikenal.
SELECT p.id, p.kode_barang, p.jumlah
FROM pesanan_lama p
LEFT JOIN barang b ON b.kode = p.kode_barang
WHERE b.kode IS NULL
ORDER BY p.id;

-- Bersihkan yatimnya, baru pasang foreign key.
DELETE FROM pesanan_lama
WHERE NOT EXISTS (SELECT 1 FROM barang b WHERE b.kode = pesanan_lama.kode_barang);

ALTER TABLE pesanan_lama
    ADD CONSTRAINT pesanan_lama_kode_barang_fkey
    FOREIGN KEY (kode_barang) REFERENCES barang (kode);

SELECT CASE
         WHEN EXISTS (SELECT 1 FROM pg_constraint
                      WHERE conname = 'pesanan_lama_kode_barang_fkey')
         THEN 'LULUS: foreign key terpasang setelah data dibersihkan'
         ELSE 'GAGAL: constraint tidak ditemukan'
       END AS uji_pemasangan;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
 id | kode_barang | jumlah 
----+-------------+--------
  2 | BRG-777     |      1
(1 row)

DELETE 1
ALTER TABLE
                    uji_pemasangan                     
-------------------------------------------------------
 LULUS: foreign key terpasang setelah data dibersihkan
(1 row)

Query auditnya adalah LEFT JOIN + IS NULL: pesanan yang setelah di-join ke barang tetap tidak menemukan pasangan adalah yatim. Versi NOT EXISTS memberikan hasil sama dan sering lebih cepat untuk tabel besar. Setelah satu baris yatim dihapus, ALTER TABLE ... ADD CONSTRAINT berhasil — dan mulai saat itu, constraint yang baru terpasang ikut dijaga oleh semua uji negatif di atas.

Data yang bersih di satu momen belum menjamin bersih di momen berikutnya — apalagi bila banyak sesi menulis bersamaan. Transaksi yang menggulung balik, savepoint yang menyelamatkan sebagian pekerjaan, dan constraint yang ditunda pemeriksaannya hingga COMMIT adalah lapisan berikutnya yang layak diuji. Chapter berikutnya menguji semuanya.

Referensi #

Berkas Sumber

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

audit-orphan.sql

Unduh berkas
sql
-- Skenario warisan sistem lama: tabel pesanan_lama dibuat TANPA foreign key
-- dan sudah berisi data termasuk kode barang yang tidak ada di tabel barang.
CREATE TABLE pesanan_lama (
    id         integer PRIMARY KEY,
    kode_barang text NOT NULL,
    jumlah     integer NOT NULL
);

INSERT INTO pesanan_lama VALUES
    (1, 'BRG-001', 2),
    (2, 'BRG-777', 1),   -- yatim: kode tidak ada di tabel barang
    (3, 'BRG-004', 5);

-- Audit integritas referensial: cari pesanan yang menunjuk barang tidak dikenal.
SELECT p.id, p.kode_barang, p.jumlah
FROM pesanan_lama p
LEFT JOIN barang b ON b.kode = p.kode_barang
WHERE b.kode IS NULL
ORDER BY p.id;

-- Bersihkan yatimnya, baru pasang foreign key.
DELETE FROM pesanan_lama
WHERE NOT EXISTS (SELECT 1 FROM barang b WHERE b.kode = pesanan_lama.kode_barang);

ALTER TABLE pesanan_lama
    ADD CONSTRAINT pesanan_lama_kode_barang_fkey
    FOREIGN KEY (kode_barang) REFERENCES barang (kode);

SELECT CASE
         WHEN EXISTS (SELECT 1 FROM pg_constraint
                      WHERE conname = 'pesanan_lama_kode_barang_fkey')
         THEN 'LULUS: foreign key terpasang setelah data dibersihkan'
         ELSE 'GAGAL: constraint tidak ditemukan'
       END AS uji_pemasangan;

buat-tabel-uji.sql

Unduh berkas
sql
-- Fixture pengujian integritas: satu tabel memuat semua jenis constraint
-- yang akan diuji: PRIMARY KEY, UNIQUE, NOT NULL, CHECK, dan FOREIGN KEY.
CREATE TABLE kategori (
    id   integer PRIMARY KEY,
    nama text NOT NULL UNIQUE
);

CREATE TABLE barang (
    kode        text PRIMARY KEY,
    nama        text NOT NULL,
    harga       integer NOT NULL CHECK (harga >= 0 AND harga <= 10000000),
    kategori_id integer NOT NULL REFERENCES kategori (id)
);

-- Nilai batas constraint CHECK harga: 0 dan 10000000 sama-sama sah.
INSERT INTO kategori VALUES
    (1, 'makanan'),
    (2, 'minuman'),
    (3, 'aksesoris');

uji-copy-atomik.sh

Unduh berkas
bash
#!/usr/bin/env bash
# Pengujian integritas saat pemuatan massal: COPY menjalankan constraint
# yang sama seperti INSERT, dan satu baris buruk menggulung balik seluruh batch.
echo '==> batch 1: tiga baris sah lewat COPY'
psql -X -q <<'SQL' 2>&1
COPY barang (kode, nama, harga, kategori_id) FROM STDIN WITH (FORMAT csv);
BRG-006,Gula Pasir,14000,2
BRG-007,Teh Celup,8000,2
BRG-008,Susu Kental,18000,2
\.
SELECT count(*) AS jumlah_barang FROM barang;
SQL

echo '==> batch 2: satu baris harga negatif tersembunyi di antara baris sah'
if printf 'BRG-009,Kopi Bubuk,25000,2\nBRG-010,Keju Slice,-45000,2\nBRG-011,Madu Hutan,32000,2\n\\.\n' \
   | psql -X -c "COPY barang (kode, nama, harga, kategori_id) FROM STDIN WITH (FORMAT csv)" 2>&1; then
  echo 'batch 2 DITERIMA (tidak sesuai harapan)'
else
  echo 'batch 2 DITOLAK'
fi

psql -X -t -A -c "SELECT 'jumlah barang tetap: ' || count(*) || ' — tidak ada baris batch 2 yang bocor masuk' AS kesimpulan FROM barang;"

uji-matriks.sql

Unduh berkas
sql
-- Matriks uji negatif: banyak kasus penolakan dalam SATU file.
-- DO block menangkap error tiap INSERT, mencatat SQLSTATE-nya,
-- lalu membandingkan dengan SQLSTATE yang diantisipasi.
CREATE TABLE hasil_uji (
    kasus          text,
    sqlstate_aktual text,
    sqlstate_harapan text
);

DO $$
DECLARE
    v_kasus  text;
    v_kode   text;
BEGIN
    FOREACH v_kasus IN ARRAY ARRAY[
        'harga di bawah batas (-1)',
        'harga di atas batas (10000001)',
        'nama NULL',
        'kode duplikat',
        'kategori tidak ada (FK yatim)'
    ] LOOP
        BEGIN
            IF v_kasus = 'harga di bawah batas (-1)' THEN
                INSERT INTO barang VALUES ('UJI-01', 'Uji A', -1, 1);
            ELSIF v_kasus = 'harga di atas batas (10000001)' THEN
                INSERT INTO barang VALUES ('UJI-02', 'Uji B', 10000001, 1);
            ELSIF v_kasus = 'nama NULL' THEN
                INSERT INTO barang VALUES ('UJI-03', NULL, 5000, 1);
            ELSIF v_kasus = 'kode duplikat' THEN
                INSERT INTO barang VALUES ('BRG-001', 'Duplikat', 5000, 1);
            ELSE
                INSERT INTO barang VALUES ('UJI-04', 'Uji C', 5000, 99);
            END IF;
            -- Baris ini hanya tercapai bila INSERT lolos — itu kegagalan uji.
            INSERT INTO hasil_uji VALUES (v_kasus, 'TIDAK DITOLAK', '23514');
        EXCEPTION WHEN OTHERS THEN
            GET STACKED DIAGNOSTICS v_kode = RETURNED_SQLSTATE;
            INSERT INTO hasil_uji VALUES (v_kasus, v_kode, NULL);
        END;
    END LOOP;
END $$;

UPDATE hasil_uji SET sqlstate_harapan = CASE
    WHEN kasus LIKE 'harga%'        THEN '23514'  -- check_violation
    WHEN kasus = 'nama NULL'        THEN '23502'  -- not_null_violation
    WHEN kasus = 'kode duplikat'    THEN '23505'  -- unique_violation
    ELSE '23503'                                 -- foreign_key_violation
END;

SELECT kasus,
       sqlstate_aktual,
       CASE WHEN sqlstate_aktual = sqlstate_harapan
            THEN 'LULUS'
            ELSE 'GAGAL: harapan ' || sqlstate_harapan || ', aktual ' || sqlstate_aktual
       END AS status
FROM hasil_uji
ORDER BY kasus;

-- Bukti akhir: tidak ada satu pun baris uji yang bocor masuk.
SELECT CASE WHEN (SELECT count(*) FROM barang WHERE kode LIKE 'UJI-%') = 0
            THEN 'LULUS: tidak ada baris tidak sah yang tersimpan'
            ELSE 'GAGAL: ada baris tidak sah yang tersimpan'
       END AS uji_kebocoran;

uji-negatif.sql

Unduh berkas
sql
-- Uji negatif bentuk paling sederhana: satu INSERT yang harus DITOLAK.
-- File ini sengaja berakhir dengan error — psql keluar dengan kode 3.
INSERT INTO barang (kode, nama, harga, kategori_id)
VALUES ('BRG-X1', 'Barang Uji', -1, 1);

uji-positif.sql

Unduh berkas
sql
-- Uji positif: data sah (termasuk nilai batas) harus DITERIMA.
-- Sebuah constraint yang salah tulis bisa menolak data yang seharusnya sah.
INSERT INTO barang (kode, nama, harga, kategori_id) VALUES
    ('BRG-001', 'Roti Tawar',   15000, 1),
    ('BRG-002', 'Kantong Belanja',    0, 3),  -- batas bawah CHECK
    ('BRG-003', 'Mesin Kopi', 10000000, 3),  -- batas atas CHECK
    ('BRG-004', 'Teh Kotak',     6000, 2);

SELECT CASE
         WHEN (SELECT count(*) FROM barang) = 4
         THEN 'LULUS: 4 baris sah tersimpan, termasuk batas bawah 0 dan batas atas 10000000'
         ELSE 'GAGAL: ekspektasi 4, aktual ' || (SELECT count(*) FROM barang)::text
       END AS uji_positif;