Repository navigation
feat: add listActiveQueries and cancelActiveQuery for running SQL queries - #53
Conversation
…ries
The runtime exposes GET /v1/sql/active and POST /v1/sql/{id}/cancel for
listing and cancelling currently running synchronous queries, but neither
was reachable from this SDK (gospice already has this pair; dotnet does not).
There was a problem hiding this comment.
Pull request overview
Adds APIs for listing and cancelling active synchronous SQL queries.
Changes:
- Adds
ActiveQueryand client operations. - Adds HTTP mock-based unit tests.
- Documents active-query management.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
SpiceClient.java |
Implements list and cancel operations. |
ActiveQuery.java |
Defines active-query metadata. |
ActiveQueriesTest.java |
Tests requests, responses, and validation. |
README.md |
Documents API usage and scope. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
withApiKey() makes SpiceClient's constructor perform a real Flight handshake, but the two tests that configure an API key only stood up the HTTP mock server, not a Flight server. The handshake against a dead default Flight address failed unpredictably by platform (reliable on Windows CI, intermittent on Linux/macOS). Start a TestFlightSqlServer with matching credentials for those cases.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/main/java/ai/spice/SpiceClient.java:1712
- This concatenates the base URI, so a supported address ending in
/produces//v1/sql/.../canceland misses the runtime route.buildProbeRequestexplicitly usesURI.resolveto avoid this case; resolve the cancellation path as well.
.uri(new URI(String.format("%s/v1/sql/%s/cancel", this.httpAddress, queryId)))
src/main/java/ai/spice/SpiceClient.java:1670
- A successful response with a missing or non-array
queriesmember is reported as “no active queries.” That hides an incompatible or malformed runtime response even though this public method promises to throw for unexpected responses; reject the invalid shape instead of silently returning an empty result.
if (queries == null || !queries.isJsonArray()) {
return Collections.emptyList();
src/main/java/ai/spice/SpiceClient.java:1619
buildProbeRequestdoes not set a per-request timeout, so after a TCP connection succeeds this synchronous call can wait indefinitely if the runtime never sends a response. Cancellation and refresh both applyHTTP_REQUEST_TIMEOUT; the listing request needs the same bound.
HttpRequest request = buildProbeRequest(this.httpAddress, "/v1/sql/active", this.apiKey);
- Resolve the cancel/list URIs against the base address instead of concatenating, so a trailing-slash httpAddress doesn't miss the route. - Bound listActiveQueries with the same request timeout cancel/refresh use. - Reject a present-but-malformed "queries" field instead of silently reporting zero active queries. - Document that runtime releases through v2.1.5 don't scope either endpoint by credential. - Fix an undeclared variable in the README example.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (3)
src/main/java/ai/spice/SpiceClient.java:1707
- Non-object entries are silently dropped, turning a malformed response into an incomplete active-query list. Reject these entries instead; otherwise callers may be told that a running query does not exist and lose the ability to cancel it.
for (JsonElement element : queries.getAsJsonArray()) {
if (element.isJsonObject()) {
result.add(GSON.fromJson(element, ActiveQuery.class));
}
}
src/main/java/ai/spice/SpiceClient.java:1700
- The endpoint contract always returns a
queriesarray, so treating a missing member as an empty result silently accepts a malformed or incompatible 200 response. That can report “no active queries” when the response was not actually valid; reject an absent member just as this method already rejects a non-array member.
This issue also appears on line 1703 of the same file.
if (queries == null) {
return Collections.emptyList();
README.md:432
- This example throws
IndexOutOfBoundsExceptionwhenever there are no active queries, which is a normal result oflistActiveQueries(). Guard the cancellation so the documented usage is safe to copy.
client.cancelActiveQuery(queries.get(0).getQueryId());
* release: prepare v0.8.0 Bumps pom.xml/Version.java to 0.8.0 and adds release notes covering the four additive feature PRs (#53-#56): search, Nsql/NsqlGenerateSql, active-query management, and async queries. japicmp.oldVersion stays at 0.6.0 rather than bumping to 0.7.0: v0.7.0 was tagged and GitHub-released but its Maven Central publish never completed, so it isn't a resolvable dependency. See the pom.xml comment for detail. * docs: reflect that #54 (search) and #55 (nsql) have merged * docs: scope v0.8.0 to search + nsql only #53 (active queries) and #56 (async queries) will not merge for this release; remove them from the release notes' What's New and reflect that in Release status instead of listing them as still-pending dependencies. * Update release notes for v0.8.0 Removed release status section and notes on features not included in v0.8.0. * Update release notes for v0.8.0 Removed testing section from release notes for v0.8.0. * Update release notes for v0.8.0 Removed highlights section from release notes for v0.8.0. * Change japicmp.oldVersion from 0.6.0 to 0.7.0 Updated the old version for japicmp to 0.7.0.
# Conflicts: # README.md # src/main/java/ai/spice/SpiceClient.java
- Reject non-object entries in the /v1/sql/active queries array instead of silently dropping them, matching the method's malformed-response contract. - Make 403 messages credential-neutral so mTLS callers aren't pointed at API keys. - Guard the README's cancellation example against an empty query list.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
src/main/java/ai/spice/SpiceClient.java:1685
- This omits the unauthenticated case: when the runtime establishes no principal, requests share the
publicscope, so separate clients without credentials can list the same queries. Documenting that boundary avoids implying isolation for no-credential clients.
This issue also appears on line 1795 of the same file.
* Results are scoped to the authenticated principal — an API key or client
* certificate — not to this {@code SpiceClient}: every client presenting the
* same credential lists the same queries. Results also cover only the one
* runtime instance this client's HTTP endpoint reaches, which behind a load
* balancer may not be the instance that received a particular query.
README.md:480
- This security-boundary summary is incomplete for clients configured without credentials. Such requests share the runtime's
publicscope, allowing unauthenticated clients to list and cancel one another's public queries; please state that explicitly.
Both calls are scoped to the authenticated API key or client certificate — not to this
`SpiceClient` instance — and reach only the one runtime process behind this client's HTTP
endpoint, which matters if that endpoint is a load balancer in front of several runtimes.
Runtime releases up to and including v2.1.5 do not scope either endpoint at all — every
caller sees and can cancel every query regardless of credential.
src/main/java/ai/spice/SpiceClient.java:1799
- The cancellation scope also needs to describe unauthenticated requests. They share the runtime's
publicscope, so one no-credential client can cancel a public query submitted by another; the current wording only explains credentialed callers.
* {@code queryId} comes from {@link #listActiveQueries()}. Cancellation is
* scoped to the authenticated principal, not to this {@code SpiceClient}: any
* client presenting the same credential can cancel the query, while an ID
* outside that scope is reported as not found. Like
* {@link #listActiveQueries()}, this reaches only the one runtime instance
…es async (#58) * docs: add active query management and async queries to v0.8.0 notes #53 (listActiveQueries/cancelActiveQuery) has merged to trunk since v0.8.0's release notes were first written, and #56 (queryAsync/ queryAsyncWithParams) is expected to merge before release. Both are purely additive, so the compatibility statement is unchanged. * Update release notes for v0.8.0 Added documentation for asynchronous query execution and active query management features. * docs: address review comments on v0.8.0 release notes - Move the AsyncQuery-handle paragraph (status/waitForCompletion/cancel) back under Async Queries; it was left stranded under Active Query Management by a later reordering of the two sections. - Reword "Public API unchanged" to "Additive, backward-compatible change" -- new public methods were added, so the API did change, just without breaking anything existing. * feat!: rename query/queryWithParams to sql/sqlWithParams; queryAsync becomes query Matches the breaking-rename pattern already shipped in the dotnet, js, and python SDKs: query()/queryWithParams() now submit SQL for asynchronous execution and return an AsyncQuery handle, and the previous synchronous, streaming behavior moves to new sql()/ sqlWithParams() methods. - SpiceClient.query(String) -> SpiceClient.sql(String) - SpiceClient.queryWithParams(String, Object...) -> SpiceClient.sqlWithParams(String, Object...) - SpiceClient.queryAsync(String) -> SpiceClient.query(String) - SpiceClient.queryAsyncWithParams(String, Object...) -> SpiceClient.queryWithParams(String, Object...) Updates every call site and cross-reference in src/main, src/test, README.md, and docs/parameterized_queries.md. Historical release notes (v0.5.0.md, v0.6.0.md) are left untouched since they accurately document what those versions actually shipped at the time. Full test suite passes unchanged in behavior -- this is a pure rename, no logic changes. * docs: document query/queryWithParams -> sql/sqlWithParams as breaking Replaces the "Additive, backward-compatible change" compatibility statement, which the rename in the previous commit made false, with an actual Breaking Changes section describing it: query()/queryWithParams() now submit for asynchronous execution and return an AsyncQuery handle; the previous synchronous, streaming behavior moved to sql()/ sqlWithParams(). Also fixes two now-stale sync-path cross-references (Nsql and Async Queries sections) that still said query()/ queryWithParams() where they meant the new sql()/sqlWithParams(). * build: excuse query/queryWithParams from the japicmp compatibility gate The gate correctly caught the intentional breaking change: query()'s return type changed from FlightStream to AsyncQuery, and queryWithParams()'s from ArrowReader to AsyncQuery, since both now submit for asynchronous execution instead of streaming results directly. Documented, scoped exclusions for exactly these two methods keep the gate meaningful for catching any other, unintended breaking change in this or a future release. Verified locally with the exact CI command (mvn checkstyle:check japicmp:cmp) -- BUILD SUCCESS, and the generated report confirms SpiceClient is otherwise fully binary- and source-compatible with the published 0.7.0.
Summary
Adds
SpiceClient.listActiveQueries()(GET /v1/sql/active) andSpiceClient.cancelActiveQuery(queryId)(POST /v1/sql/{id}/cancel) to inspect and cancel synchronous queries running on the runtime.Test plan
ActiveQueriesTest) against a local mock HTTP server, covering success, error-status handling, and client-side validation of the query ID.mvn compileand the new test suite pass locally.