Schema Builder API

API dan menu Database Tables di Studio untuk membuat dan mengelola tabel transaksi milik tenant sendiri (DDL/DML) tanpa menulis SQL mentah.

Gambaran Umum

Schema Builder memungkinkan tenant membuat dan mengelola tabel data miliknya sendiri di database AlurKerja — tanpa menulis SQL. Cocok dipakai SOP/integrasi eksternal yang butuh tabel transaksi kustom (mis. tabel yang dipakai bersama beberapa proses BPMN, atau data yang perlu dikelola lewat API alih-alih lewat form). Tabel bisa dikelola lewat REST API (halaman ini) atau lewat menu Database Tables di Studio (lihat Mengelola dari Studio).

Setiap tenant punya schema database sendiri. API ini hanya mengizinkan operasi pada tabel yang dibuat lewat API ini sendiri — tabel bawaan sistem (mis. tabel transaksi otomatis per proses BPMN, lihat Tabel Transaksi Manual) tidak pernah bisa disentuh atau tertimpa oleh API ini.

Endpoint ini hanya bisa diakses oleh Admin SOP tenant (bukan Member biasa), karena bisa membuat/mengubah/menghapus tabel dan datanya.

Autentikasi

Semua endpoint butuh header Authorization: Bearer <token> (JWT AlurKerja, sama seperti endpoint API lain) dan :tenantId di path adalah ID tenant tujuan.

Semua respons memakai amplop yang sama: success, message (kalau berhasil), data, dan error (kalau gagal). Pada contoh di bawah, {tenantId} dan {table} diganti dengan ID tenant dan nama tabel Anda.

Selalu aktif

Seluruh fitur di halaman ini — tipe varchar(N), endpoint /check, endpoint /references, dan menu Database Tables di Studio — selalu aktif di report-service dan Studio. Tidak ada lagi feature flag yang perlu dinyalakan, dan instalasi on-premise baru langsung mendapatkan fiturnya tanpa konfigurasi tambahan.

Untuk admin instalasi

Variabel FEATURE_FLAG_DATABASE_TABLES (report-service) dan VITE_FEATURE_FLAG_DATABASE_TABLES (Studio) sudah tidak dibaca lagi. Instalasi yang pernah menyetelnya, baik true maupun false, tidak perlu mengubah apa pun: nilai lama diabaikan dan boleh dihapus dari .env, values Helm, atau environment container. Perilaku untuk instalasi yang sebelumnya menyalakan flag ini tidak berubah.

Tipe kolom yang didukung

Saat membuat tabel atau menambah kolom, tiap kolom harus salah satu dari tipe berikut:

Tipe (dikirim di request)Disimpan sebagai
textteks bebas panjang
varcharteks dengan batas panjang N karakter. Wajib disertai length 1–10.000
integerbilangan bulat
bigintbilangan bulat besar
numericangka desimal presisi
booleanbenar/salah
datetanggal
timestamptanggal + jam (dengan timezone)
jsonbdata JSON bebas

Tiap tabel otomatis punya 3 kolom bawaan yang tidak perlu (dan tidak boleh) didefinisikan sendiri: id (primary key), created_at, updated_at.

Nama tabel dan nama kolom harus huruf kecil, diawali huruf, hanya huruf/angka/underscore (^[a-z][a-z0-9_]*$), maksimal 63 karakter. Nama tabel tidak boleh diawali catalog_, pg_, atau report_. Jumlah kolom per tabel dibatasi pengaturan instalasi SCHEMA_BUILDER_MAX_COLUMNS (bawaan 100); nilai yang berlaku dikembalikan di field max_columns pada setiap respons tabel.

varchar(N)

varchar dikirim sebagai dua field terpisah: type berisi "varchar" dan length berisi bilangan bulat 1 sampai 10.000. Panjang lebih dari itu pakai text.

Kolom varchar(20)
{ "name": "kode_cabang", "type": "varchar", "length": 20, "nullable": false }
  • length wajib untuk varchar dan ditolak untuk tipe lain (400).
  • length harus bilangan bulat JSON. "20" atau 20.5 dijawab 400 length must be an integer.
  • Menulis nilai yang lebih panjang dari length lewat Tambah data atau Ubah data dijawab 400 value for column "kode_cabang" is longer than 20 characters. Data tidak pernah dipotong.
  • Di respons, kolom varchar membawa field length; field itu tidak muncul untuk tipe lain.

Tabel

Buat tabel

POST /api/v1/report/{tenantId}/schema/tables
Request body
{
  "name": "penjualan_harian",
  "description": "Rekap penjualan harian per cabang",
  "columns": [
    { "name": "cabang", "type": "text", "nullable": false },
    { "name": "total", "type": "numeric", "nullable": false },
    { "name": "catatan", "type": "text", "nullable": true }
  ]
}
Response 201
{
  "success": true,
  "message": "Table created successfully",
  "data": {
    "id": 1,
    "table_name": "penjualan_harian",
    "columns": [
      { "name": "cabang", "type": "text", "nullable": false },
      { "name": "total", "type": "numeric", "nullable": false },
      { "name": "catatan", "type": "text", "nullable": true }
    ],
    "description": "Rekap penjualan harian per cabang",
    "created_at": "2026-09-23T10:00:00Z",
    "updated_at": "2026-09-23T10:00:00Z",
    "max_columns": 100
  }
}

Request ditolak (400) kalau nama tabel sudah dipakai tabel aktif lain di schema tenant ini — baik tabel yang dibuat lewat API ini sebelumnya, maupun tabel bawaan sistem (mis. tabel transaksi proses BPMN). Ini mencegah tabel baru menimpa data yang sudah ada. Nama tabel yang sudah dihapus boleh dipakai lagi untuk tabel baru.

Daftar tabel

GET /api/v1/report/{tenantId}/schema/tables

Mengembalikan semua tabel yang dibuat tenant ini lewat API ini (tabel bawaan sistem tidak ikut muncul).

Detail tabel

GET /api/v1/report/{tenantId}/schema/tables/{table}

Mengembalikan definisi kolom tabel. 404 kalau tabel tidak pernah dibuat lewat API ini (termasuk kalau nama itu adalah tabel bawaan sistem).

Hapus tabel

DELETE /api/v1/report/{tenantId}/schema/tables/{table}?confirm=true

Parameter confirm=true wajib — tanpa itu, request ditolak (400) dan tabel tidak disentuh sama sekali. Ini mencegah tabel terhapus tidak sengaja.

Ini adalah DROP TABLE, bukan rename atau backup — seluruh baris di tabel itu ikut hilang. Kalau datanya masih dipakai, ekspor dulu lewat Lihat data. Setelah dihapus, nama tabelnya bebas dan boleh dipakai lagi.

Menghapus tabel tidak diblokir oleh Question yang masih memakainya. Question akan gagal dijalankan setelah tabelnya hilang, jadi periksa dulu lewat Question yang memakai tabel.

Kolom

Struktur tabel yang sudah dibuat bisa diubah lewat endpoint kolom berikut. Semua perubahan DDL dan pembaruan registry berjalan dalam satu transaksi: kalau gagal, tidak ada yang berubah.

EndpointFungsi
POST …/schema/tables/{table}/columnsTambah kolom
DELETE …/schema/tables/{table}/columns/{column}?confirm=trueHapus kolom
POST …/schema/tables/{table}/columns/{column}Ubah nama, nullable, dan/atau tipe kolom
POST …/schema/tables/{table}/columns/{column}/checkPeriksa dulu perubahan tipe/nullable tanpa menerapkannya

Kolom bawaan id, created_at, dan updated_at tidak bisa diubah atau dihapus.

Tambah kolom

POST /api/v1/report/{tenantId}/schema/tables/{table}/columns
Request body
{ "name": "kode_cabang", "type": "varchar", "length": 50 }
FieldWajibKeterangan
nameyaNama kolom baru; aturan penamaan sama seperti di atas, tidak boleh sama dengan kolom lain.
typeyaSalah satu tipe kolom.
lengthuntuk varchar1–10.000. Ditolak untuk tipe lain.
copy_fromtidakNama kolom lain di tabel yang sama. Nilainya disalin (di-cast ke tipe baru) ke kolom baru, dalam transaksi yang sama.

Kolom yang ditambah selalu nullable — tidak ada opsi nullable, karena menambah kolom NOT NULL ke tabel yang sudah berisi baris akan gagal di Postgres. Pakai ubah kolom dengan "nullable": false setelah semua baris terisi.

Response 201
{
  "success": true,
  "message": "Column added successfully",
  "data": {
    "id": 3,
    "table_name": "cabang",
    "columns": [
      { "name": "nama", "type": "text", "nullable": false },
      { "name": "kode_cabang", "type": "varchar", "nullable": true, "length": 50 }
    ],
    "created_at": "2026-10-06T09:00:00Z",
    "updated_at": "2026-10-08T03:15:00Z",
    "max_columns": 100
  }
}

Kalau copy_from dipakai, respons ikut membawa "affected_rows" (jumlah baris yang disalin). Kalau nilai sumber tidak muat di kolom varchar(N) yang baru, request dijawab 409 (lihat konflik data) dan kolom baru tidak dibuat.

Error yang mungkin: 400 kalau nama/tipe tidak valid, nama sudah dipakai, atau jumlah kolom melewati max_columns; 404 kalau tabel tidak ada (kolom copy_from yang tidak ada dijawab 400); 409 kalau tabel sudah memakai habis 1.600 slot kolom milik Postgres (termasuk kolom yang sudah dihapus) — buat tabel baru dan pindahkan datanya.

Hapus kolom

DELETE /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}?confirm=true

confirm=true wajib. Tanpa itu, tidak ada yang dihapus: kalau ada Question yang menyebut kolom itu, jawabannya 409 berisi daftar Question; kalau tidak ada, 400 minta confirm=true.

Response 409 — tanpa confirm, ada Question yang memakai kolom
{
  "success": false,
  "error": "2 report question(s) reference this column; check them, then pass confirm=true to proceed anyway",
  "data": {
    "questions": [
      { "id": 12, "name": "Penjualan per cabang" },
      { "id": 31, "name": "Top 10 cabang" }
    ]
  }
}
Response 200 — dengan confirm=true
{
  "success": true,
  "message": "Column dropped successfully (863 row(s) affected)",
  "data": {
    "table_name": "cabang",
    "column_name": "kode_cabang",
    "affected_rows": 863,
    "referencing_questions": [
      { "id": 12, "name": "Penjualan per cabang" }
    ]
  }
}

affected_rows adalah jumlah baris yang kehilangan data kolom itu. Penghapusan kolom tidak bisa dibatalkan. referencing_questions hanya muncul kalau ada Question yang menyebut kolom itu walau confirm=true sudah meloloskan penghapusan. Kolom terakhir di tabel tidak bisa dihapus (400).

Ubah kolom

POST /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}

Satu request boleh berisi kombinasi new_name, nullable, dan type (+ length); minimal satu wajib. Urutan penerapannya tetap: tipe → nullable → nama, semuanya dalam satu transaksi.

FieldKeterangan
new_nameNama baru kolom. Wajib disertai ?confirm=true (lihat di bawah).
nullabletrue untuk mengizinkan kosong, false untuk mewajibkan nilai.
typeTipe baru, sesuai aturan ubah tipe.
lengthPanjang untuk type: "varchar". Hanya boleh bersama type.
Request body — ubah varchar(50) menjadi varchar(20)
{ "type": "varchar", "length": 20 }

Contoh ini dijalankan pada kolom kode_cabang bertipe varchar(50) di tabel cabang. Mengecilkan varchar adalah perubahan yang diperiksa dulu: report-service mengunci tabel, lalu menghitung baris yang nilainya lebih panjang dari 20 karakter.

Kalau semua nilai muat, perubahan diterapkan:

Response 200
{
  "success": true,
  "message": "Column updated successfully",
  "data": {
    "id": 3,
    "table_name": "cabang",
    "columns": [
      { "name": "nama", "type": "text", "nullable": false },
      { "name": "kode_cabang", "type": "varchar", "nullable": true, "length": 20 }
    ],
    "created_at": "2026-10-06T09:00:00Z",
    "updated_at": "2026-10-08T03:20:00Z",
    "max_columns": 100
  }
}

Kalau masih ada nilai yang lebih panjang, perubahan ditolak dan tidak ada data yang dipotong atau diubah:

Response 409
{
  "success": false,
  "error": "12 rows are longer than 20 characters",
  "data": {
    "reason": "too_long",
    "violating_rows": 12,
    "sample_ids": [4, 17, 23],
    "max_length": 30
  }
}

violating_rows adalah jumlah baris yang tidak muat, sample_ids berisi paling banyak 3 id contoh, dan max_length adalah nilai terpanjang yang ada di kolom (di sini 30 karakter, jadi varchar(30) atau lebih besar masih lolos). Perbaiki atau pendekkan baris itu dulu, atau pilih panjang yang lebih besar, lalu ulangi.

Angka pada contoh 409 (12 baris, id 4/17/23, panjang maksimum 30) hanya ilustrasi; angka sebenarnya bergantung isi tabel Anda. Bentuk respons mengikuti kode report-service. Gunakan /check untuk melihat hasil yang sama tanpa menerapkan perubahan.

Ganti nama kolom

Mengganti nama kolom bisa merusak Question yang menulis nama lama di SQL-nya, jadi new_name selalu butuh ?confirm=true. Tanpa confirm=true, tidak ada yang berubah: kalau ada Question yang menyebut kolom itu, jawabannya 409 berisi daftarnya (bentuk sama seperti Hapus kolom); kalau tidak ada, 400 renaming a column requires confirm=true.

POST /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}?confirm=true
Request body
{ "new_name": "kode" }

Respons 200 sama seperti di atas, ditambah referencing_questions kalau ada Question yang menyebut kolom itu. Question tidak ikut diperbarui otomatis; perbarui SQL-nya sendiri.

Jadikan kolom wajib diisi

Request body
{ "nullable": false }

Kalau masih ada baris yang kosong di kolom itu, perubahan ditolak dengan jawaban 409 yang memuat jumlah baris dan id contoh:

Response 409
{
  "success": false,
  "error": "863 rows have no value in catatan",
  "data": { "reason": "has_nulls", "violating_rows": 863, "sample_ids": [1, 2, 3] }
}

Konflik data

Respons 409 dengan data.reason berarti ada baris yang menghalangi perubahan:

reasonArtiField lain
too_longAda nilai yang lebih panjang dari varchar tujuanviolating_rows, sample_ids, max_length
has_nullsAda baris kosong, sehingga kolom tidak bisa dibuat wajibviolating_rows, sample_ids

409 juga dipakai untuk the table is busy with another operation, try again. Ubah tipe menulis ulang tabel dengan lock eksklusif; kalau tabel sedang dipakai transaksi lain lebih dari 5 detik, atau perubahan berjalan lebih dari 60 detik, perubahan dibatalkan. Ulangi beberapa saat kemudian.

Periksa perubahan kolom

POST /api/v1/report/{tenantId}/schema/tables/{table}/columns/{column}/check

Menjawab "apakah perubahan ini akan diterima?" memakai hitungan baris yang sama dengan perubahan sungguhan, tetapi tidak mengubah apa pun. Body-nya type (+ length) dan/atau nullable; minimal salah satu wajib (new_name tidak diperiksa di sini).

Request body
{ "type": "varchar", "length": 20 }

Perubahan yang akan diterima:

Response 200 — diizinkan
{
  "success": true,
  "message": "Column change checked",
  "data": { "allowed": true }
}

Perubahan yang akan ditolak karena data:

Response 200 — tidak diizinkan
{
  "success": true,
  "message": "Column change checked",
  "data": {
    "allowed": false,
    "reason": "too_long",
    "violating_rows": 12,
    "sample_ids": [4, 17, 23],
    "max_length": 30
  }
}

Perhatikan bahwa /check menjawab 200 baik allowed bernilai true maupun false; pelanggaran data tidak dijawab 409 di sini. Kalau tipe tujuan memang tidak diizinkan oleh aturan ubah tipe, jawabannya 400 dengan pesan penjelas. Bila type dan nullable dikirim bersamaan, yang dilaporkan adalah masalah pertama yang ditemukan, tipe lebih dulu baru nullable. Studio memanggil endpoint ini sebelum menyimpan perubahan tipe atau Make required.

Aturan ubah tipe

Tidak semua perubahan tipe boleh langsung dijalankan. Aturan dibagi tiga: langsung (tidak mungkin gagal karena data), diperiksa dulu (bisa gagal karena data, jadi dihitung dulu), dan ditolak.

Dari → keAturan
varchar(N) → varchar(M), M > NLangsung diizinkan
varchar(N) → textLangsung diizinkan
varchar(N) → varchar(M), M < NDiperiksa dulu
text → varchar(M)Diperiksa dulu
tipe lain yang dikenal → varchar(M)Diperiksa dulu
varchar(N) → varchar(N) (sama)Ditolak: column is already type "varchar(N)"
varchar → integer, date, dan tipe lain selain textDitolak

Diperiksa dulu artinya: di dalam transaksi dan lock yang sama dengan ALTER, report-service menghitung baris yang nilainya lebih panjang dari M. Kalau ada satu saja, perubahan ditolak 409 dengan jumlah baris dan beberapa contoh id. Data tidak pernah dipotong.

Untuk tipe selain varchar, daftar perubahan yang langsung diizinkan adalah:

DariBoleh ke
integerbigint, numeric, text
bigintnumeric, text
numerictext
booleantext
datetext, timestamp
timestamptext
jsonbtext

Perubahan date → timestamp memakai tengah malam zona waktu Asia/Jakarta, tidak bergantung pada pengaturan zona waktu sesi database.

Kombinasi lain — misalnya text → integer, numeric → integer, timestamp → date, atau text → jsonb — ditolak (400) karena bisa gagal di tengah data atau mengubah arti data. Jalurnya: tambah kolom baru dengan tipe tujuan dan copy_from kolom lama, periksa hasilnya, lalu hapus kolom lama. Kalau penyalinan gagal, transaksi dibatalkan dan kolom baru tidak pernah ada.

Jangan menambah atau mengubah kolom secara manual lewat psql langsung ke schema tenant. Endpoint Data (rows) memvalidasi nama kolom terhadap definisi yang tersimpan di registry internal, bukan terhadap struktur tabel yang sebenarnya di database — jadi kolom hasil ALTER manual tetap ditolak (400, unknown column "...") walaupun kolomnya benar-benar ada di database. Sebaliknya, kolom yang dihapus manual lewat psql membuat request tambah/ubah data gagal di level database. Registry dan tabel fisik harus tetap sejalan; gunakan endpoint kolom di atas.

Question yang memakai tabel

GET /api/v1/report/{tenantId}/schema/tables/{table}/references

Mengembalikan Question yang SQL-nya menyebut nama tabel ini (sebagai kata utuh, tidak membedakan huruf besar/kecil), beserta satu baris cuplikan SQL, dan untuk tiap kolom tabel berapa Question di antaranya yang menyebut kolom itu (0 tetap ditampilkan). Studio memakainya untuk kolom Used in dan untuk daftar di dialog hapus tabel.

Response 200
{
  "success": true,
  "message": "Table references retrieved successfully",
  "data": {
    "questions": [
      {
        "id": 12,
        "name": "Penjualan per cabang",
        "snippet": "SELECT kode_cabang, COUNT(*) FROM <<cabang>> GROUP BY kode_cabang",
        "updated_at": "2026-10-02T08:30:00Z"
      }
    ],
    "columns": {
      "nama": 0,
      "kode_cabang": 1
    }
  }
}

snippet adalah baris pertama SQL yang menyebut tabel (maksimal 200 karakter). updated_at bernilai null untuk Question lama yang tidak punya cap waktu. 404 kalau tabel tidak ada.

Pemeriksaan ini berbasis teks: report-service mencari nama tabel dan kolom di teks SQL Question, bukan menganalisis SQL-nya, dan tidak ada relasi tersimpan antara Question dan tabel. Hasilnya adalah peringatan, bukan jaminan — Question yang menyusun nama tabel secara dinamis tidak terdeteksi, dan nama yang kebetulan sama dengan kata lain di SQL bisa ikut terhitung. Endpoint ini hanya informasi dan tidak pernah memblokir hapus tabel.

Pemeriksaan serupa sudah ada di Hapus kolom dan Ganti nama kolom, yang menjawab 409 berisi daftar Question sampai confirm=true dikirim.

Mengelola dari Studio

Selain lewat API, tabel bisa dikelola dari menu Database Tables di Studio, di grup DESIGN tepat di bawah Master Data. Menu ini selalu tampil, dan hanya bisa dipakai Admin SOP — Member biasa tidak punya akses ke Studio. Semua aksi di bawah memanggil endpoint yang dijelaskan di halaman ini.

Daftar tabel dan buat tabel

Saat pertama kali dibuka dan belum ada tabel, halaman Database Tables menampilkan keadaan kosong. Di bagian atas ada penjelasan singkat Master Data or Database Tables? beserta tautan Go to Master Data, lalu pesan No database tables yet yang menjelaskan bahwa tabel dibuat untuk data yang ditulis oleh integrasi atau proses, dan dibaca Question sebagai <<table_name>>. Dari sini Anda bisa langsung membuat tabel pertama.

Halaman Database Tables di Studio saat belum ada tabel, menampilkan penjelasan Master Data atau Database Tables dan pesan No database tables yet beserta tombol untuk membuat tabel

Klik New table.

Isi Name tabel. Nama divalidasi di form dengan aturan yang sama seperti API (huruf kecil, diawali huruf, hanya huruf/angka/underscore). Di bawahnya tertulis bentuk yang dipakai Question: <<nama_tabel>>. Isi Description bila perlu.

Di bagian Columns, tambahkan baris kolom: Name, Type, Length (aktif hanya untuk varchar, isi 1–10.000), dan Nullable. Tipe yang bisa dipilih adalah text, varchar, integer, bigint, numeric, boolean, date, dan timestamp. Kolom id, created_at, dan updated_at ditambahkan otomatis.

Simpan. Tabel baru muncul di daftar dan detailnya terbuka.

Form New table di Studio dengan isian Name dan Description serta daftar kolom berisi Name, Type, Length, dan Nullable

Setelah ada tabel, halaman menampilkan semuanya dengan kolom Name, Description, Columns (jumlah kolom), dan Created. Kotak Search tables... menyaring daftar, dan penjelasan singkat beserta tautan ke Master Data tetap tampil di atas daftar.

Halaman Database Tables di Studio yang menampilkan daftar tabel dengan kolom Name, Description, Columns, dan Created, serta kotak pencarian dan tombol New table

Kelola kolom di detail tabel

Klik sebuah tabel untuk membuka detailnya. Di bagian atas ada nama tabel, tombol <<nama_tabel>> untuk menyalin nama yang dipakai di Question, dan menu Table actions. Tab Columns menampilkan kolom dengan Name, Type, Nullable, dan Used in (jumlah Question yang menyebut kolom itu, dari endpoint /references). Kolom bawaan tampil di bawah label Added automatically dan tidak bisa diubah.

Detail tabel di Studio dengan tab Columns yang menampilkan daftar kolom, tipe, nullable, dan jumlah Question yang memakainya, serta kolom bawaan id, created_at, dan updated_at

Tambah kolom. Klik tambah kolom, isi Name, Type, dan Length untuk varchar. Kolom baru selalu nullable, sama seperti di API.

Dialog Add column di Studio dengan isian nama kolom, pilihan tipe varchar, dan isian Length

Aksi per kolom. Menu di ujung baris kolom berisi Rename, Change type, Make required (atau Make optional untuk kolom yang sudah wajib), dan Delete column.

  • Rename dan Delete column menampilkan Question yang menyebut kolom itu sebelum Anda mengonfirmasi.
  • Make required memeriksa dulu lewat /check. Kalau masih ada baris kosong, dialog menolak dan menampilkan jumlah baris beserta contoh id, sehingga Anda bisa mengisinya lebih dulu.
  • Change type hanya menawarkan tipe tujuan yang sah dari tipe asal sesuai aturan ubah tipe. Sebelum Save, Studio memanggil /check; kalau ada nilai yang terlalu panjang, dialog menampilkan jumlah baris, contoh id, dan panjang maksimum yang ada, mis. "12 baris lebih panjang dari 20 karakter". Untuk perubahan lain, dialog mengarahkan ke jalur tambah kolom baru → salin data → hapus kolom lama.
Dialog Change type di Studio yang menampilkan tipe saat ini, pilihan tipe baru varchar dengan Length, dan hasil pemeriksaan berupa jumlah baris yang lebih panjang dari batas

Kalau tabel sedang sibuk, Studio menampilkan pesan bahwa tabel sedang dipakai operasi lain; ulangi beberapa saat kemudian.

Hapus tabel

Buka Table actions → Delete table. Dialog menampilkan jumlah baris yang akan hilang, mengingatkan bahwa integrasi dan proses yang menulis ke tabel itu akan gagal, dan memeriksa Question yang menyebut tabel itu (Checking questions…) lalu menampilkannya. Tombol Delete table baru aktif setelah Anda mengetik nama tabel persis di kotak konfirmasi. Penghapusan tidak bisa dibatalkan; nama tabel yang sudah dihapus boleh dipakai lagi.

Dialog Delete table di Studio dengan peringatan jumlah baris, daftar Question yang memakai tabel, dan kotak untuk mengetik nama tabel sebagai konfirmasi

Tab Data

Tab Data menampilkan isi tabel dalam mode hanya baca: ada paginasi dan kotak filter = value di tiap kolom, tetapi tidak ada tombol tambah, ubah, atau hapus baris. Nilai kosong ditampilkan sebagai NULL. Isi tabel ditulis oleh integrasi dan proses lewat API data. Bila tabel masih kosong, tertulis No rows yet.

Tab Data di detail tabel Studio yang menampilkan baris tabel dalam mode hanya baca dengan kotak filter di tiap kolom dan tombol halaman sebelumnya dan berikutnya

Pakai tabel di Question

Di editor Question, tabel Schema Builder muncul di grup Database Tables pada pemilih tabel, terpisah dari tabel proses dan Master Data. Question menyebut tabel dengan namanya, mis. <<penjualan_harian>>.

Database Tables dan Master Data

Master DataDatabase Tables (Schema Builder)
Untuk apaData referensi form: pilihan dropdown, relasiTabel SQL untuk integrasi dan laporan
PenyimpananRecord JSON di bpm-serviceTabel Postgres sungguhan di schema tenant
Tipe kolom14 tipe tampilan (select, file, email, rich text…)8 tipe SQL ditambah varchar(N)
Isi dataLewat Studio, termasuk impor XLSXLewat API dan proses; Studio hanya menampilkan
Dibaca QuestionYa, sebagai master_<slug>Ya, dengan nama tabelnya

Aturan praktisnya: kalau datanya dipilih di form, pakai Master Data. Kalau datanya ditulis sistem lain atau proses dan dibaca laporan, pakai Database Tables.

Data (rows)

Tambah data

POST /api/v1/report/{tenantId}/schema/tables/{table}/rows
Request body — bisa lebih dari satu baris sekaligus
{
  "rows": [
    { "cabang": "Surabaya", "total": 1250000, "catatan": null },
    { "cabang": "Malang", "total": 870500, "catatan": "termasuk diskon" }
  ]
}

Setiap baris dalam satu request harus punya kolom yang persis sama (tidak boleh baris 1 punya kolom catatan sementara baris 2 tidak). Satu request menampung 1–500 baris.

Response 201
{ "success": true, "message": "Rows inserted successfully", "data": { "affected_rows": 2 } }

Lihat data

GET /api/v1/report/{tenantId}/schema/tables/{table}/rows?page=0&limit=50&cabang=Surabaya
  • page (default 0) dan limit (default 50, maksimal 500) untuk paginasi.
  • Parameter query lain diperlakukan sebagai filter kesamaan (kolom=nilai), boleh lebih dari satu (digabung dengan AND). Filter pada kolom yang tidak ada di tabel akan ditolak (400).
Response 200
{
  "success": true,
  "message": "Rows retrieved successfully",
  "data": {
    "rows": [
      { "id": 1, "cabang": "Surabaya", "total": 1250000, "catatan": null, "created_at": "...", "updated_at": "..." }
    ],
    "page": 0,
    "limit": 50,
    "total": 1
  }
}

Ubah data

PATCH /api/v1/report/{tenantId}/schema/tables/{table}/rows
Request body
{
  "where": { "cabang": "Surabaya" },
  "set": { "total": 1480000 }
}

where dan set wajib diisi (tidak boleh kosong) — endpoint ini sengaja tidak mendukung update ke seluruh baris sekaligus tanpa kondisi, supaya update yang salah tidak menimpa semua data.

Hapus data

DELETE /api/v1/report/{tenantId}/schema/tables/{table}/rows
Request body
{ "where": { "cabang": "Malang" } }

where wajib diisi untuk alasan yang sama dengan update — hapus seluruh baris di satu tabel tidak didukung endpoint ini (hapus tabelnya lewat endpoint Hapus Tabel kalau memang itu maksudnya).

Error yang umum terjadi

StatusPenyebab
400Body request tidak valid, tipe kolom tidak didukung, nama tabel/kolom melanggar aturan penamaan, jumlah kolom melewati max_columns, atau kolom yang direferensikan (where/set/filter) tidak dikenal.
400varchar tanpa length, length di luar 1–10.000 atau bukan bilangan bulat, length pada tipe selain varchar, atau nilai baris lebih panjang dari kolom varchar.
400Hapus tabel atau kolom tanpa ?confirm=true, ganti nama kolom tanpa ?confirm=true, atau perubahan tipe yang tidak diizinkan aturan ubah tipe.
401Token tidak ada/tidak valid, atau bukan Admin SOP.
400Kolom yang diubah, dihapus, atau dijadikan sumber copy_from tidak ada di tabel (column "..." not found on this table).
404Tabel tidak ditemukan — tidak pernah dibuat lewat API ini untuk tenant tersebut.
409Perubahan kolom ditahan oleh data (too_long, has_nulls), kolom dipakai Question dan belum ada confirm=true, tabel sedang sibuk, atau slot kolom Postgres habis.

Jejak audit

Setiap panggilan create/drop table, tambah/hapus/ubah kolom, insert, update, dan delete tercatat (siapa, kapan, statement, berhasil/gagal) untuk keperluan audit internal. Kontak tim AlurKerja kalau butuh melihat riwayat perubahan sebuah tabel.