API /query/exec และ /query/execBatch
ประตูเขียนข้อมูลทั่วไป (INSERT/UPDATE/DELETE) สำหรับหน้าจอที่ยังไม่มี process endpoint ของตัวเอง. ผ่านสามชั้นตรวจ: (1) สิทธิ์ตามชนิดคำสั่งที่ router, (2) รูปแบบ SQL ที่ domain, (3) "write lockdown" ที่ห้ามแตะตารางเงิน/สต็อก/GL จากเบราว์เซอร์. เหตุผลอ่าน ADR-0006 /query/exec รับเฉพาะ DML + ตารางระบบต้อง level>=9 (ถูกแทนด้วย ADR-0011), ADR-0009 /query/execBatch เขียนหลาย statement ใน transaction เดียว (atomic), ADR-0011 ล็อก SQL เขียนจาก browser: ตารางเงิน/สต๊อก/GL ผ่าน /api/process เท่านั้น; ตารางเงินให้ใช้ API เครื่องยนต์ process /process/* — เส้นทาง สิทธิ์ ขนาดบอดี้ (Process engine run API) แทน; ฝั่งอ่านดู API ประตูอ่านข้อมูล /query — list, limit, batch (Query gateway read endpoints).
ชั้นที่ 1: router และ right (router.go:85-102)
Auth(Bearer access token) แล้วตามด้วยqueryWriteGuard = RequireRight(ex.Rights, processhttp.QueryRequiredRight)— ตั้งเมื่อมีextra[0].Rights(router.go:85-87) ไม่งั้นqueryWriteGuardเป็น nil และไม่ตรวจ rightQueryRequiredRight(process/permission.go:135-161) ตัดสิน right จากคำกริยาแรกของ SQL:insert-> Add,update-> Edit,delete-> Delete, อื่น ๆ -> Write. สำหรับexecBatch: ทุกคำสั่งเป็น delete -> Delete; คำสั่งแรก insert -> Add; update -> Edit. คำสั่งเขียนsml_audit_logแยกชนิดจากvalues (1|2|3,. ผู้ใช้ที่มีสิทธิ์เมนูระดับ Admin (rights.Admin) ผ่านทุกชนิด; Write = Add หรือ Edit (middleware/permission.go:43-60) (ดู การตรวจสิทธิ์เมนูของ route process (Process route rights), สิทธิ์เมนูและ middleware RequireRight (Menu rights / permission))- ไม่ผ่านสิทธิ์ = 403 พร้อมข้อความไทยจาก
deniedMessages(middleware/permission.go:27);peekJSONอ่านบอดี้สูงสุด 1 MB เพื่อดูคำสั่งแล้วคืนบอดี้ให้ handler (permission.go:231)
ชั้นที่ 2: รูปแบบ SQL (domain/query/query.go)
NewWriteStatement(query.go:335): รับคำสั่งเดียวที่ขึ้นต้นINSERT/UPDATE/DELETEหรือWITH ... <DML>; ปฏิเสธ DDL/คำสั่งผู้ดูแล (ErrNotWriteOnly= 400)ValidateUserLevel(query.go:361): level < 9 ห้ามเขียนsml_user_list,sml_database_list*,erp_option(ErrForbiddenTable-> 403)- ชุด
execBatch:NewWriteBatchStatements(query.go:394) ไม่เกิน 1000 คำสั่ง (MaxExecBatchStatements, query.go:77) และรวมไม่เกิน 2 MB (MaxExecBatchBytes, query.go:81)
ชั้นที่ 3: write lockdown (เฉพาะคำขอจากเบราว์เซอร์ ClientRequest:true)
ValidateClientPolicy (domain/query/write_policy.go:77-103):
- SQL ที่ใช้ identifier แบบ
U&"..."หรือหาตารางเป้าหมายไม่ได้ =ErrAdminOnlyWrite - ตารางเงิน/สต็อก/GL (ตระกูล
ic_trans*,ap_ar_trans*,as_trans*,cb_trans*,cb_chq_list,gl_journal*และอื่น ๆ ตาม regexmoneyTablePatternที่ write_policy.go:24) หรือic_inventoryที่แก้คอลัมน์ยอดคงเหลือ (balance_qty,average_costฯลฯ) =ErrMoneyTableViaExecข้อความ "…only be changed through /api/process" - level < 9 เขียนได้เฉพาะตารางใน allow-list เดิม (
write_policy_legacy.go:9) นอกนั้นErrAdminOnlyWrite - ทั้งสองกรณีแมปเป็น
KindForbidden= 403 (service.go:104-112)
POST /query/exec
- Request:
{"sql":"UPDATE ic_inventory SET name_1 = '<ชื่อ>' WHERE code = '<itemCode>' RETURNING code"}(handler.go:227) - Response 200:
{"rowsAffected":1,"rows":[{...}]}(rowsคืนเมื่อมีRETURNING) - Errors: 400 SQL ไม่ใช่ DML เดี่ยว/ว่าง/DB error (gateway error ถูกแมปเป็น validation, service.go:123), 401 token, 403 right/ตารางต้องห้าม
- timeout 30 วินาที (service.go:115); log เก็บแค่ keyword, ตาราง, hash ของ SQL (ไม่เก็บ SQL ดิบ) handler.go:243-260
POST /query/execBatch
- Request:
{"sql":["INSERT ...","UPDATE ..."]}(handler.go:281) - Response 200:
[{"rowsAffected":n,"rows":[...]},...]ตามลำดับ - ทำใน transaction เดียว: คำสั่งไหนพัง ย้อน (rollback) ทั้งชุด ไม่มีผลบางส่วน (service.go:137-171, ADR-0009 /query/execBatch เขียนหลาย statement ใน transaction เดียว (atomic)); ข้อความ error ของ lockdown ระบุ
statement N - ตัวอย่าง:
POST /query/execBatch
{"sql":["INSERT INTO ic_color (code, name_1) VALUES ('<code>','<name>')","DELETE FROM ic_color WHERE code = '<oldCode>'"]}
ผู้เรียกฝั่งหน้าเว็บ
POST /api/query/exec(src/app/api/query/exec/route.ts:12): ต้องมีsqlเป็นสตริง ไม่งั้น 400 "กรุณาระบุคำสั่ง SQL"; ส่งต่อ/query/exec(route.ts:31) ผ่านfetchServiceWithAuthRetry; ผลห่อ{ok:true,data}; ตัวช่วยqueryExecที่src/lib/query-client.ts:44POST /api/query/execBatch(execBatch/route.ts:12): ต้องเป็น array ไม่ว่าง;queryExecBatchที่ query-client.ts:63- งานเงิน/สต็อก/GL ของหน้าจอไม่ใช้ route นี้ แต่ใช้
saveTrans,saveDoc,rebuildStockฯลฯ ใน query-client.ts (ดู Process action กลุ่มเอกสาร/ธุรกรรม — trans, doc, lookup, cheque, tax, transgrid (Transaction process actions), ตัวเรียก API และตัวประกอบ SQL ฝั่งเบราว์เซอร์ (api-client, query-client, sml-query-builder))
ที่มาในโค้ด
backend/internal/adapter/inbound/http/router.go:85-102backend/internal/adapter/inbound/http/process/permission.go:135-161,231backend/internal/adapter/inbound/http/query/handler.go:218-340backend/internal/application/query/service.go:95-171backend/internal/domain/query/query.go:61-81,335-425,write_policy.go:12-103,write_policy_legacy.go:5-41frontend/src/app/api/query/exec/route.ts,execBatch/route.ts;frontend/src/lib/query-client.ts:44,63