feat(server): Spring Boot REST gateway over the query engine#19
Conversation
POST /queries plans and synchronously executes read-only SQL, returning a
structured JSON result (positional column arrays + name/qualifiedName/type
metadata, truncated/hint) or EXPLAIN plan text. The /tables endpoints list
tables, expose a table's static typed schema, and accept opt-in CSV upload
(disabled by default).
A single QueryService choke point plans, attaches an always-present
fail-closed budget (clamped to hard caps), bounds concurrency with a
semaphore, branches EXPLAIN with zero budget, and audits every request, so
governance cannot be bypassed. GlobalExceptionHandler maps engine ErrorCodes
to HTTP: 4xx where the caller can act, scrubbed 5xx (generic message + id,
paths logged server-side only) for engine faults. Strict table-name
validation, work-dir path containment, and a streamed upload size cap guard
the write surface.
Spring enters only through the server module (spring-boot-dependencies BOM
import, no reparent); the parent POM and engine stay Spring-free. JUnit/JMH
version management moves to the engine module so the server takes JUnit from
the Spring BOM. CI switches to `mvnw clean package` to exercise the boot jar.
Engine: add BudgetKind{TUPLES,TIME} on QueryBudgetExceededException so a
tuple-budget breach maps to 429 and a wall-clock breach to 504.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@claude review this PR |
|
Claude finished @JinBa1's task in 3m 21s —— View job PR Review: feat(server): Spring Boot REST gateway over the query engine
This is a well-structured PR with strong security thinking. The fail-closed budget, choke-point architecture, and path-scrubbing are all solid. A few issues worth addressing before merge. Bug: Misleading 415 error message
@Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(...) {
return new ResponseEntity<>(ErrorResponse.of("UNSUPPORTED_MEDIA_TYPE",
"Unsupported Content-Type; this endpoint expects text/csv."), ...);
}This override applies to all endpoints, but the message hardcodes "text/csv". A client sending Race condition: TOCTOU table-count cap
if (catalog.tableCount() >= properties.upload().maxTables()) {
throw new TableLimitExceededException(properties.upload().maxTables());
}
// ... later ...
registered = catalog.register(name, target);The count check and One approach: have Minor:
|
What
A Spring Boot REST gateway (
server/module) over the in-memory query engine: a guarded, read-only-by-construction, resource-budgeted HTTP+JSON front door, with an OpenAPI/Swagger contract.Endpoints
POST/queriesGET/tablesGET/tables/{name}POST/tables/{name}QueryResponseuses positional column arrays + per-column{name, qualifiedName, type}so a joinSELECT *with duplicate bare names stays unambiguous.Design
QueryServicechoke point — plans, attaches an always-present fail-closed budget (defaulted + clamped to hard caps; the engine's "null = unlimited" path is unreachable through the server), bounds concurrency with a semaphore, branches EXPLAIN with zero tuple budget, and audits every request. Governance/audit cannot be bypassed.GlobalExceptionHandlermaps engine error codes to HTTP — 4xx where the caller can act (parse 400; unsupported/unknown-column/type-mismatch/unknown-table-in-query 422), tuple budget → 429 +Retry-After, time budget → 504, concurrency saturation → 429. 5xx bodies are scrubbed (generic message + correlation id; raw messages, which can embed filesystem paths, are logged server-side only). Stack traces are never serialized.^[A-Za-z0-9_]{1,64}$) before any path is built, work-dir path containment, and a streamed upload size cap (413) so an oversized body is never fully buffered.spring-boot-dependenciesBOM import, no reparent); the parent POM andenginestay Spring-free.Engine change
Adds
BudgetKind{TUPLES, TIME}toQueryBudgetExceededExceptionso the gateway can split a tuple-budget breach (429, retry narrower) from a wall-clock breach (504).Testing
BudgetPolicy,ConcurrencyLimiter),@WebMvcTestper controller (full status/JSON/error-mapping), and@SpringBootTestintegration (query round-trip, upload→query, fail-closed budget, table-count cap → 507, path-scrubbed 5xx, concurrent queries)../mvnw clean packageis green and exercises the Spring Boot repackage; CI now runsclean package.Reviewed via a multi-agent adversarial pass; confirmed findings fixed (notably: upload row-count computed before registration so an I/O fault can't orphan a live-but-reported-failed table; concurrency-permit pinned in the load test so it holds on low-CPU CI).