API /query (ฝั่งอ่าน): list, limit, batch
ประตู SQL สำหรับหน้าจอทั่วไปของ frontend: ส่ง SQL ข้อความมา Go ตรวจ แล้วรันบนฐานบริษัทของ token (provider/database มาจาก claims ไม่ใช่ body). แนวคิดและเหตุผลอ่านที่ ADR-0005 Query Gateway เริ่มแบบอ่านอย่างเดียว + สัญญา wire ใหม่ และ ADR-0008 /query/batch รัน SELECT หลายตัวในคำขอเดียว; ฝั่งเขียนอยู่ที่ API ประตูเขียนข้อมูล /query/exec และ /query/execBatch (Query exec endpoints); ตัวประกอบ SQL ฝั่งหน้าเว็บอยู่ที่ ตัวเรียก API และตัวประกอบ SQL ฝั่งเบราว์เซอร์ (api-client, query-client, sml-query-builder).
สิทธิ์และ header
- ทุก route ใต้
/queryผ่านAuth(router.go:90-102):Authorization: Bearer <accessToken>; ไม่มี/ผิด = 401missing or invalid access token list,limit,batchใช้Authอย่างเดียว ไม่ผ่านRequireRight(เฉพาะexec/execBatchมีqueryWriteGuard)- เวลาจำกัดต่อคำสั่ง
queryTimeout= 30 วินาที (application/query/service.go:16) และ timeout รวมของ request 60 วินาที (router.go:64)
กฎ SQL ที่ใช้กับทุก endpoint ฝั่งอ่าน
NewStatement (domain/query/query.go) รับเฉพาะ SELECT หรือ WITH เท่านั้น; ปฏิเสธ DML/DDL และ pattern ต้องห้ามใน SELECT (INTO, pg_sleep, dblink, copy ฯลฯ ที่ query.go:59). ข้อผิดพลาดตรวจ SQL = KindValidation = 400. ฝั่ง Next ตรวจซ้ำก่อนส่ง (เช่น limit/route.ts ตัด literal แล้วเช็กคำต้องห้าม).
| ค่าคงที่ | ค่า | ที่มา |
|---|---|---|
MaxLimit (แถวต่อหน้าของ /query/limit) |
1000 | domain/query/query.go:66 |
MaxListRows (แถวสูงสุดของ /query/list) |
50000 | query.go:69 |
MaxBatchStatements (คำสั่งต่อ /query/batch) |
20 | query.go:73 |
POST /query/list
- Request:
{"sql":"SELECT ... "}(handler.go:103) - Response 200: อาร์เรย์ของแถว
[{"col":value,...}](ตรง ๆ ไม่มี envelope) - เกิน 50000 แถว =
ErrTooManyRows(400); คำสั่งช้ากว่า 1 วินาที (slowQueryThreshold, handler.go:28) ถูกบันทึก log โดยปิดค่า literal ด้วยmaskSQLLiterals - ตัวอย่าง:
POST /query/list {"sql":"SELECT code, name_1 FROM ic_inventory WHERE code = '<itemCode>'"}
POST /query/limit
- Request:
{"sql":"SELECT ...","offset":0,"limit":50,"includeTotal":true}(handler.go:163) - Response 200:
{"totalRecords":123,"rows":[...]}โดยtotalRecordsมีเมื่อincludeTotal=true(นับด้วยqueryTotal, service.go:219) offset< 0 ถูกบีบเป็น 0 และlimit<= 0 หรือ > 1000 ถูกบีบเป็น 1000 (NewPagination, query.go:454-460)- ตัวอย่าง:
{"sql":"SELECT * FROM ar_customer ORDER BY code","offset":0,"limit":50,"includeTotal":true}
POST /query/batch
- Request:
{"sql":["SELECT ...","SELECT ..."]}ไม่เกิน 20 คำสั่ง (handler.go:347; service.go:180) - Response 200: อาร์เรย์ของอาร์เรย์แถว
[[...rows ของคำสั่ง 1...],[...คำสั่ง 2...]]ตามลำดับ - คำสั่งใดพังจะหยุดทั้งชุดและคืน error ของคำสั่งนั้น (service.go:176-215); ใช้ connection เดียวกัน (ถ้า gateway มี
QueryBatchจะส่งทั้งชุดครั้งเดียว ไม่งั้นรันทีละคำสั่ง) ภายใต้ timeout 30 วินาทีรวมทั้งชุด; ข้อความ error ระบุลำดับstatement Nในโหมดรันทีละคำสั่ง (service.go:204-212) - เกิน 20 คำสั่ง =
ErrTooManyStatements(400)
รูปแบบ error
{"error":{"message":"..."}} (response/response.go; ดู รูปแบบ error และ response ของ API (apperr + response helper)): validation 400, token 401, forbidden 403, อื่น ๆ 500 ข้อความกลาง internal server error.
ผู้เรียกฝั่งหน้าเว็บ
Next route ห่อผลเป็น {ok:true,data} (ผิดพลาด {ok:false,error,code}) — ดู เส้นทาง API ฝั่ง Next.js ที่ส่งต่อไป Go backend (API proxy routes):
POST /api/query/list(src/app/api/query/list/route.ts:12): รับsqlหรือquery; ฟังก์ชันqueryListในsrc/lib/query-client.ts:16POST /api/query/limit(limit/route.ts:12): ค่าเริ่มต้นlimit=50,offset=0,includeTotal=true; ถ้าsource="sql_utility"ต้อง level >= 9 (403FORBIDDEN);queryLimitที่ query-client.ts:25POST /api/query/batch(batch/route.ts:12): ตรวจ array ไม่ว่าง และไม่เกิน 20 ก่อนส่ง;queryBatchที่ query-client.ts:54- ทั้งหมดใช้
requireSession->getValidAccessToken->fetchServiceWithAuthRetry(api-server.ts:248, 555, 388) ซึ่งลองซ้ำเมื่อ 502/503/504 สูงสุด 3 ครั้ง และต่ออายุ token เมื่อเจอ 401; ไม่มี token = 401NO_TOKEN
ที่มาในโค้ด
backend/internal/adapter/inbound/http/router.go:90-103backend/internal/adapter/inbound/http/query/handler.go:28,103,163,347backend/internal/application/query/service.go:16,32,58,180,219backend/internal/domain/query/query.go:35-81,432,454frontend/src/app/api/query/list/route.ts,limit/route.ts,batch/route.tsfrontend/src/lib/query-client.ts:16,25,54,frontend/src/lib/api-server.ts:248,388,555