Query Deklaratif
Konfigurasi query berbasis template dengan parameter binding dinamis
RESTForge menggunakan pendekatan deklaratif untuk menentukan query SQL yang digunakan oleh setiap endpoint. Seluruh konfigurasi query didefinisikan di dalam file payload JSON tanpa menulis kode secara manual. Setiap properti query memiliki peran spesifik dan melayani endpoint tertentu.
Daftar Properti Query (Query Properties)
| Properti | Wajib | File Reference | Endpoint yang Dilayani |
|---|---|---|---|
datatablesQuery | Ya | ✓ | /datatables |
viewQuery | Tidak | ✓ | /read, /first, /read-composite (header) |
viewName | Tidak | — | /read, /first, /lookup, /read-composite (header) |
exportQuery | Tidak | ✓ | /export |
detailQuery | Tidak | ✓ | /read-composite (detail) |
Prioritas Resolusi Query (Query Resolution Priority)
Setiap endpoint menentukan query SQL berdasarkan prioritas resolusi berikut:
/datatables → datatablesQuery → fallback: SELECT * FROM readSource
/read → viewName → viewQuery → tableName
/first → viewName → viewQuery → tableName
/lookup → viewName → tableName
/export → exportQuery → fallback: SELECT {fieldName} FROM {tableName}
/read-composite → Header: viewName → viewQuery → tableName
Detail: detailQuery → fallback: SELECT * FROM {detailTable}datatablesQuery tidak menjadi fallback untuk /read. Endpoint /read dan /datatables memiliki sumber data yang independen.
Properti Query secara Detail (Detailed Properties)
datatablesQuery
Query utama yang digunakan oleh endpoint /datatables. Properti ini wajib ada di setiap payload.
Deklarasi inline:
{
"datatablesQuery": "select a.item_product_id, a.product_code, a.product_name, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id"
}Deklarasi file reference:
{
"datatablesQuery": "file:query/item_product_list.sql"
}viewQuery
Query yang berfungsi sebagai "virtual view" pengganti database VIEW. Digunakan oleh endpoint /read, /first, dan header pada /read-composite. Cocok untuk situasi di mana pembuatan VIEW di database tidak memungkinkan (misalnya keterbatasan akses DDL).
{
"viewQuery": "select a.item_product_id, a.product_code, a.product_name, a.category_id, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id"
}viewName
Nama database VIEW yang sudah dibuat di server database. Jika didefinisikan, endpoint /read, /first, /lookup, dan header pada /read-composite akan membaca data dari VIEW ini. Operasi write (insert, update, delete) tetap menggunakan tableName.
{
"tableName": "item_product",
"viewName": "v_item_product"
}DDL untuk membuat VIEW:
create or replace view v_item_product as
select a.item_product_id, a.product_code, a.product_name,
a.category_id, b.category_name, a.selling_price,
a.stock, a.uom, a.is_active
from item_product a
inner join category b on b.category_id = a.category_id;exportQuery
Query untuk endpoint /export yang menentukan data yang diekspor ke file Excel. Mendukung alias kolom dan transformasi nilai di level SQL.
{
"exportQuery": "file:query/item_product_export.sql"
}Contoh file SQL dengan alias bahasa Indonesia:
select a.product_code as "Kode Produk",
a.product_name as "Nama Produk",
b.category_name as "Kategori",
a.selling_price as "Harga Jual",
a.stock as "Stok",
case when a.is_active then 'Aktif' else 'Tidak Aktif' end as "Status"
from item_product a
inner join category b on b.category_id = a.category_id
order by b.category_name, a.product_namedetailQuery
Query yang digunakan oleh endpoint /read-composite untuk mengambil detail item berdasarkan foreign key dari header. Terletak di dalam struktur masterDetail.detailConfig.
{
"masterDetail": {
"enabled": true,
"detailTable": "stock_inbound_item",
"foreignKey": "stock_inbound_id",
"detailConfig": {
"detailQuery": "file:query/stock-inbound-detailquery.sql"
}
}
}Query harus berisi satu placeholder untuk foreign key value sesuai syntax native database:
| Database | Placeholder | Contoh |
|---|---|---|
| PostgreSQL | $1 | WHERE stock_inbound_id = $1 |
| MySQL | ? | WHERE stock_inbound_id = ? |
| Oracle | :1 | WHERE stock_inbound_id = :1 |
Saat detailQuery menggunakan file reference, isi file SQL harus ditulis dengan placeholder native sesuai database target. Tidak ada konversi placeholder otomatis pada mode file.
Format File Reference
Semua properti query yang mendukung file reference menggunakan format file:relative/path/to/query.sql. Path bersifat relatif terhadap lokasi file payload JSON.
Struktur folder yang direkomendasikan:
payload/
├── item-product.json
├── supplier.json
├── stock-inbound.json
└── query/
├── item_product_list.sql
├── item_product_export.sql
├── stock-inbound-datatables.sql
└── stock-inbound-detailquery.sqlContoh Penggunaan (Usage Examples)
Tabel Sederhana tanpa Relasi (Simple Table)
{
"tableName": "category",
"primaryKey": "category_id",
"fieldName": ["category_id", "category_name", "is_active"],
"datatablesQuery": "select category_id, category_name, is_active from category",
"action": {
"datatables": true,
"read": true,
"first": true,
"create": true,
"update": true,
"delete": true
}
}Pada tabel sederhana tanpa relasi, cukup definisikan datatablesQuery. Endpoint /read dan /first menggunakan fallback ke tableName secara langsung.
Tabel dengan Relasi — Menggunakan Database VIEW
{
"tableName": "item_product",
"primaryKey": "item_product_id",
"viewName": "v_item_product",
"fieldName": [
"item_product_id", "product_code", "product_name",
"category_id", "category_name", "selling_price", "stock", "is_active"
],
"datatablesQuery": "select a.item_product_id, a.product_code, a.product_name, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id",
"action": {
"datatables": true,
"read": true,
"first": true,
"lookup": true,
"create": true,
"update": true
}
}Dengan viewName, endpoint /first dan /lookup juga mendapatkan category_name karena membaca dari VIEW.
Tabel dengan Relasi — Tanpa Database VIEW
Situasi yang sama, tetapi menggunakan viewQuery sebagai pengganti karena tidak dapat membuat VIEW di database:
{
"tableName": "item_product",
"primaryKey": "item_product_id",
"fieldName": [
"item_product_id", "product_code", "product_name",
"category_id", "category_name", "selling_price", "stock", "is_active"
],
"datatablesQuery": "select a.item_product_id, a.product_code, a.product_name, b.category_name, a.selling_price, a.stock, a.is_active from item_product a inner join category b on b.category_id = a.category_id",
"viewQuery": "file:query/item_product.sql",
"action": {
"datatables": true,
"read": true,
"first": true,
"create": true,
"update": true
}
}Perbedaan dengan viewName: pada konfigurasi viewQuery, endpoint /first dan /lookup membaca langsung dari tableName sehingga tidak mendapatkan kolom dari relasi. Hanya endpoint /read yang mendapatkan data relasi melalui viewQuery.
Master-Detail Composite Read
{
"tableName": "stock_inbound",
"primaryKey": "stock_inbound_id",
"fieldName": [
"stock_inbound_id", "inbound_number", "inbound_date",
"warehouse_id", "supplier_id", "total_items", "total_qty", "total_amount", "status"
],
"datatablesQuery": "file:query/stock-inbound-datatables.sql",
"action": {
"datatables": true,
"read": true,
"readComposite": true,
"createComposite": true,
"updateComposite": true
},
"masterDetail": {
"enabled": true,
"detailTable": "stock_inbound_item",
"foreignKey": "stock_inbound_id",
"detailConfig": {
"tableName": "stock_inbound_item",
"primaryKey": "stock_inbound_item_id",
"detailQuery": "file:query/stock-inbound-detailquery.sql"
}
}
}Endpoint /read-composite menggunakan tableName untuk header dan detailQuery untuk detail item yang melakukan JOIN ke item_product untuk mengambil product_code dan product_name.
Hubungan fieldName dengan Query (fieldName and Query)
fieldName berfungsi sebagai whitelist kolom yang memfilter output response. Mekanisme ini berlaku pada semua endpoint read.
| Skenario | Perilaku |
|---|---|
Query mengembalikan kolom lebih banyak dari fieldName | Kolom ekstra dibuang sebelum dikirim ke client |
fieldName berisi kolom yang tidak ada di hasil query | Kolom tersebut tidak muncul di response (tanpa error) |
| Kolom dari relasi (JOIN) | Harus didaftarkan di fieldName agar muncul di response |
fieldName tidak mengubah SQL query yang dieksekusi. Filtering terjadi setelah query dieksekusi, pada level aplikasi melalui formatResponseData().
Panduan Pemilihan (Decision Guide)
| Kondisi | Properti yang Digunakan |
|---|---|
| Tabel sederhana tanpa relasi | datatablesQuery saja |
Endpoint /first dan /lookup perlu data relasi | viewName (database VIEW) |
| Tidak dapat membuat VIEW di database | viewQuery (virtual view) |
| Kolom export berbeda dari tampilan UI | exportQuery |
| Master-detail dengan JOIN pada detail item | detailQuery di masterDetail.detailConfig |
| Query panjang yang sulit dibaca inline | File reference (file:query/xxx.sql) |
Ringkasan Properti vs Endpoint (Property vs Endpoint Summary)
| Properti | /datatables | /read | /first | /lookup | /export | /read-composite |
|---|---|---|---|---|---|---|
datatablesQuery | Utama | — | — | — | — | — |
viewName | — | Prioritas 1 | Prioritas 1 | Utama | — | Header: Prioritas 1 |
viewQuery | — | Prioritas 2 | Prioritas 2 | — | — | Header: Prioritas 2 |
exportQuery | — | — | — | — | Utama | — |
detailQuery | — | — | — | — | — | Detail: Utama |
tableName | Fallback | Fallback | Fallback | Fallback | Fallback | Fallback |
Langkah Selanjutnya (Next Steps)
- Query Builder untuk membangun query secara programatik di Processor
- Sort Columns untuk pengurutan multi-kolom
- CRUD Composite untuk operasi master-detail