Tidak semua data berupa teks atau angka. Logo toko berformat PNG, file PDF katalog produk, hash kata sandi, hingga blob terenkripsi — semuanya adalah rangkaian byte mentah yang tidak mengenal konsep huruf. PostgreSQL menyediakan tipe bytea (dibaca byte-a, singkatan dari byte array) khusus untuk data seperti ini. Pada chapter ini kita belajar menulis nilai bytea dalam format heksadesimal, menyimpan berkas kecil di dalam tabel, menghitung checksum dengan ekstensi pgcrypto, dan mengenali batas kapan bytea berhenti menjadi pilihan yang tepat.

Menyimpan Byte Mentah #

Kolom bytea menyimpan rangkaian byte apa adanya: byte ke-0 bisa bernilai 0x00, byte berikutnya 0xFF, dan server tidak pernah mencoba menafsirkannya sebagai teks. Penyimpanannya transparan — PostgreSQL memakai mekanisme TOAST untuk memindahkan nilai besar keluar dari baris utama secara otomatis, sehingga nilai bytea dapat mencapai kisaran 1 GB per kolom. Batas praktis yang jauh lebih rendah akan kita bahas di bagian akhir chapter, karena ukuran besar berdampak langsung pada performa query.

Deklarasinya tidak butuh parameter: cukup tulis bytea pada definisi kolom, misalnya isi bytea NOT NULL. Berbeda dari text yang menyimpan karakter, bytea menyimpan byte — dua hal yang sering tertukar pemula padahal konsekuensinya besar: nilai text punya encoding dan fungsi string, sedangkan bytea hanya punya operasi tingkat byte.

Tiga Cara Menulis Nilai bytea #

PostgreSQL menerima beberapa format input untuk bytea. Cara yang paling umum adalah format heksadesimal: string yang diawali \x lalu diikuti pasangan digit hex, dua digit untuk tiap byte. Tiga pernyataan berikut menghasilkan byte yang persis sama:

-- Tiga cara menulis bytea yang sama: byte DE AD BE EF
SELECT '\xDEADBEEF'::bytea AS kutip_biasa;

SELECT E'\\xDEADBEEF'::bytea AS kutip_escape;

SELECT decode('DEADBEEF', 'hex') AS fungsi_decode;

SELECT octet_length('\xDEADBEEF'::bytea) AS panjang_byte;

Output yang Dihasilkan:

text
 kutip_biasa 
-------------
 \xdeadbeef
(1 row)

 kutip_escape 
--------------
 \xdeadbeef
(1 row)

 fungsi_decode 
---------------
 \xdeadbeef
(1 row)

 panjang_byte 
--------------
            4
(1 row)

Mari kita bedah ketiga cara tersebut:

  1. '\xDEADBEEF'::bytea bekerja karena sejak PostgreSQL mengaktifkan standard_conforming_strings secara default, string biasa yang diawali \x langsung dikenali sebagai format hex saat dikonversi ke bytea.
  2. E'\\xDEADBEEF'::bytea memakai escape string (E'...') dengan backslash ganda. Bentuk ini diperlukan pada klien lama atau skrip yang mematikan standard_conforming_strings, karena di mode tersebut backslash tunggal akan ditafsirkan sebagai awal escape sequence.
  3. decode('DEADBEEF', 'hex') memanggil fungsi konversi dan sama sekali tidak menyentuh aturan escape string. Inilah cara paling tahan banting: string sumbernya hanya berisi digit hex murni, jadi tidak ada kebingungan kutip maupun backslash.

Perhatikan juga hasilnya: server selalu menampilkan bytea dalam format \xdeadbeef — awalan \x dengan digit hex huruf kecil — terlepas dari cara kita menulisnya. Fungsi octet_length() mengembalikan jumlah byte sungguhan (4), bukan panjang teks deadbeef yang berjumlah 8 karakter.

Masih ada satu format lagi yang perlu Anda kenali walau jarang dipakai: format escape warisan. Sebelum format hex menjadi bawaan, bytea ditulis dengan byte non-cetak direpresentasikan sebagai oktal tiga digit — nilai \xDEADBEEF dalam format escape tampil sebagai \336\255\276\357, sementara karakter tercetak tampil apa adanya. Perilaku output ini diatur setting bytea_output (bawaannya hex sejak PostgreSQL 9.0); mode escape hanya relevan untuk klien atau alat lama yang belum mengerti format hex. Anda akan menemukan format ini di dump-dump tua, jadi cukup kenali bentuknya — jangan tulis yang baru dengannya.

Menyimpan Berkas Kecil di Tabel #

Mari kita pakai bytea dalam tabel nyata. Tabel berkas berikut menyimpan potongan data biner yang lazim dijumpai: signature file. Setiap format file punya byte pembuka yang khas — PNG selalu dimulai dengan 89 50 4E 47 0D 0A 1A 0A, PDF dengan %PDF-1.7 dalam ASCII-nya, dan ZIP dengan PK\x03\x04:

CREATE TABLE berkas (
    id    integer PRIMARY KEY,
    nama  text NOT NULL,
    jenis text NOT NULL,
    isi   bytea NOT NULL
);

-- Signature biner file nyata: 8 byte pertama PNG, PDF, dan 4 byte pertama ZIP
INSERT INTO berkas (id, nama, jenis, isi) VALUES
    (1, 'logo-toko.png', 'image/png',        decode('89504E470D0A1A0A', 'hex')),
    (2, 'katalog.pdf',   'application/pdf',  decode('255044462D312E37', 'hex')),
    (3, 'paket.zip',     'application/zip',  decode('504B0304', 'hex'));

SELECT id, nama, jenis,
       octet_length(isi) AS ukuran,
       encode(isi, 'hex') AS isi_hex
FROM berkas
ORDER BY id;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 3
 id |     nama      |      jenis      | ukuran |     isi_hex      
----+---------------+-----------------+--------+------------------
  1 | logo-toko.png | image/png       |      8 | 89504e470d0a1a0a
  2 | katalog.pdf   | application/pdf |      8 | 255044462d312e37
  3 | paket.zip     | application/zip |      4 | 504b0304
(3 rows)

Dua fungsi baru muncul di file tersebut. octet_length(isi) mengukur ukuran data dalam byte — berguna untuk membatasi unggahan tanpa membaca isinya. encode(isi, 'hex') mengubah bytea menjadi teks hex yang bisa dibaca manusia — kebalikan dari decode(). Kolom isi sendiri tidak kita tampilkan mentah karena isinya byte; hasil encode()-lah yang terbaca sebagai 89504e470d0a1a0a.

Checksum dengan pgcrypto #

Kebutuhan paling umum bytea di aplikasi bisnis bukan menyimpan file, melainkan menyimpan hash. Ekstensi pgcrypto menyediakan fungsi digest() yang menghasilkan hash biner — SHA-256 misalnya selalu tepat 32 byte. Karena bytea adalah wadah alami untuk hash tersebut, keduanya sering dipakai berpasangan:

sql hash-digest.sql
-- pgcrypto menyediakan fungsi digest() untuk hash biner
CREATE EXTENSION IF NOT EXISTS pgcrypto;

ALTER TABLE berkas ADD COLUMN checksum bytea;

UPDATE berkas SET checksum = digest(isi, 'sha256');

SELECT id, nama,
       octet_length(checksum) AS panjang_checksum,
       encode(checksum, 'hex') AS sha256
FROM berkas
ORDER BY id;

Output yang Dihasilkan:

text
CREATE EXTENSION
ALTER TABLE
UPDATE 3
 id |     nama      | panjang_checksum |                              sha256                              
----+---------------+------------------+------------------------------------------------------------------
  1 | logo-toko.png |               32 | 4c4b6a3be1314ab86138bef4314dde022e600960d8689a2c8f8631802d20dab6
  2 | katalog.pdf   |               32 | 86edbaa24831badfa0a8b04bb410141e2ee4182b6d0014493fe262a7a331c20b
  3 | paket.zip     |               32 | 8dcc7e601606217f3b754766511182a916b17e9a26a94c9d887104eba92e9bb2
(3 rows)

File ini melakukan tiga hal berurutan: memasang ekstensi pgcrypto (sekali per database, aman diulang dengan IF NOT EXISTS), menambah kolom checksum bytea, lalu mengisinya dengan digest(isi, 'sha256') untuk setiap baris. Hasilnya selalu 32 byte berapa pun ukuran inputnya. Hash ini deterministik: isi yang sama selalu menghasilkan hash yang sama, sehingga cocok untuk memverifikasi integritas data atau mendeteksi duplikat.

Operasi Tingkat Byte #

Saat data biner perlu dipotong atau diperiksa byte demi byte, PostgreSQL menyediakan perangkat lengkap yang mirip fungsi string, tetapi bekerja pada byte:

-- Membaca byte pada posisi tertentu (index mulai dari 0)
SELECT get_byte('\xCAFE'::bytea, 0) AS byte_pertama;

-- Mengubah byte pada posisi tertentu, sisanya tetap
SELECT set_byte('\xCAFE'::bytea, 1, 255) AS byte_kedua_diubah;

-- Memotong bytea: mulai byte ke-3 (index 2) sepanjang 2 byte
SELECT substring('\xDEADBEEFCAFE'::bytea FROM 3 FOR 2) AS potongan;

-- Menggabungkan dua nilai bytea dengan operator ||
SELECT '\x00FF'::bytea || '\xCAFE'::bytea AS gabungan;

-- Teks biasa otomatis dikonversi menjadi bytea (ASCII-nya)
SELECT 'Toko Maju Jaya'::bytea AS teks_jadi_biner;

Output yang Dihasilkan:

text
 byte_pertama 
--------------
          202
(1 row)

 byte_kedua_diubah 
-------------------
 \xcaff
(1 row)

 potongan 
----------
 \xbeef
(1 row)

  gabungan  
------------
 \x00ffcafe
(1 row)

        teks_jadi_biner         
--------------------------------
 \x546f6b6f204d616a75204a617961
(1 row)

Tiga hal yang perlu dicermati dari hasilnya. get_byte() mengembalikan angka 202 karena 0xCA = 202 dalam desimal — pembacaan byte selalu berupa angka, bukan digit hex. set_byte() mengubah byte kedua (0xFE) menjadi 0xFF dan membiarkan byte pertama tetap. substring() memotong mulai byte ketiga — posisinya dihitung dari 1, berbeda dari index get_byte() yang mulai dari 0. Terakhir, teks 'Toko Maju Jaya' dikonversi menjadi bytea berisi kode ASCII tiap hurufnya (T menjadi 0x54), dan operator || menggabungkan bytea sama seperti menggabungkan teks.

Input yang Salah Ditolak Langsung #

Sama seperti tipe-tipe khusus lain di section ini, bytea memvalidasi inputnya saat dikonversi. Digit hex yang tidak sah membuat seluruh pernyataan gagal:

SELECT '\xDEADXX'::bytea;

Output yang Dihasilkan:

text
psql:input-invalid.sql:1: ERROR:  invalid hexadecimal digit: "X"
LINE 1: SELECT '\xDEADXX'::bytea;
               ^

Server menolak string tersebut karena X bukan digit heksadesimal yang valid, dan pesan errornya menyebutkan digit bermasalah beserta posisinya. Data biner yang lolos ke kolom bytea dijamin formatnya sah — tidak ada setengah-byte atau digit ganjil yang tersimpan diam-diam.

Kapan Tidak Memakai bytea #

Bytea sering dipakai berlebihan untuk file besar, dan itu jebakan performa. Setiap kali baris terbaca, kolom bytea ikut termuat; query SELECT yang tidak menampilkan isi tetap membayar biaya bila TOAST memindahkan datanya. Untuk berkas besar, ada dua pola yang lebih sehat:

PolaCara kerjaCocok untuk
Simpan pathFile di object storage/disk, kolom text menyimpan alamatnyaFoto produk, lampiran besar
Large ObjectAPI lo menyimpan data terpisah, tabel hanya menyimpan OID-nyaFile multi-GB, akses sebagian

Pola simpan path adalah yang paling umum di aplikasi web modern: database menyimpan metadata (nama, jenis, ukuran, path), sementara berkasnya hidup di storage yang memang dirancang untuk itu. Large object (lo) adalah fitur PostgreSQL yang menempatkan data biner di tabel sistem terpisah sehingga bisa dibaca per potongan tanpa memuat seluruh nilai ke memori.

Dengan bytea, katalog tipe data kita kini meliputi data biner. Berikutnya kita beranjak ke tipe yang tampak seperti teks padahal bukan: alamat jaringan. Tipe inet, cidr, dan macaddr menyimpan IP address dan alamat MAC dengan validasi serta operator jaringan bawaan — topik chapter Network Address Types.

Referensi #

Berkas Sumber

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

format-input.sql

Unduh berkas
sql
-- Tiga cara menulis bytea yang sama: byte DE AD BE EF
SELECT '\xDEADBEEF'::bytea AS kutip_biasa;

SELECT E'\\xDEADBEEF'::bytea AS kutip_escape;

SELECT decode('DEADBEEF', 'hex') AS fungsi_decode;

SELECT octet_length('\xDEADBEEF'::bytea) AS panjang_byte;

hash-digest.sql

Unduh berkas
sql
-- pgcrypto menyediakan fungsi digest() untuk hash biner
CREATE EXTENSION IF NOT EXISTS pgcrypto;

ALTER TABLE berkas ADD COLUMN checksum bytea;

UPDATE berkas SET checksum = digest(isi, 'sha256');

SELECT id, nama,
       octet_length(checksum) AS panjang_checksum,
       encode(checksum, 'hex') AS sha256
FROM berkas
ORDER BY id;

input-invalid.sql

Unduh berkas
sql
SELECT '\xDEADXX'::bytea;

operasi-byte.sql

Unduh berkas
sql
-- Membaca byte pada posisi tertentu (index mulai dari 0)
SELECT get_byte('\xCAFE'::bytea, 0) AS byte_pertama;

-- Mengubah byte pada posisi tertentu, sisanya tetap
SELECT set_byte('\xCAFE'::bytea, 1, 255) AS byte_kedua_diubah;

-- Memotong bytea: mulai byte ke-3 (index 2) sepanjang 2 byte
SELECT substring('\xDEADBEEFCAFE'::bytea FROM 3 FOR 2) AS potongan;

-- Menggabungkan dua nilai bytea dengan operator ||
SELECT '\x00FF'::bytea || '\xCAFE'::bytea AS gabungan;

-- Teks biasa otomatis dikonversi menjadi bytea (ASCII-nya)
SELECT 'Toko Maju Jaya'::bytea AS teks_jadi_biner;

tabel-berkas.sql

Unduh berkas
sql
CREATE TABLE berkas (
    id    integer PRIMARY KEY,
    nama  text NOT NULL,
    jenis text NOT NULL,
    isi   bytea NOT NULL
);

-- Signature biner file nyata: 8 byte pertama PNG, PDF, dan 4 byte pertama ZIP
INSERT INTO berkas (id, nama, jenis, isi) VALUES
    (1, 'logo-toko.png', 'image/png',        decode('89504E470D0A1A0A', 'hex')),
    (2, 'katalog.pdf',   'application/pdf',  decode('255044462D312E37', 'hex')),
    (3, 'paket.zip',     'application/zip',  decode('504B0304', 'hex'));

SELECT id, nama, jenis,
       octet_length(isi) AS ukuran,
       encode(isi, 'hex') AS isi_hex
FROM berkas
ORDER BY id;