From 2bde2ee97ccd1dd47bd3ab76ca841b4448a7bfd9 Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Fri, 21 Aug 2026 14:57:16 -0700 Subject: [PATCH 1/6] 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. --- docs/release_notes/v0.8.0.md | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/release_notes/v0.8.0.md b/docs/release_notes/v0.8.0.md index bc4a59f..6833c6e 100644 --- a/docs/release_notes/v0.8.0.md +++ b/docs/release_notes/v0.8.0.md @@ -29,8 +29,42 @@ System.out.println(response.getData()); String sql = client.nsqlGenerateSql(new NsqlRequest("how many trips were over 10 miles?")); ``` +### 📋 Active Query Management + +`listActiveQueries()` reports the synchronous queries currently running on the runtime, and `cancelActiveQuery(queryId)` cancels one. The runtime doesn't hand a query's ID back to the client that submitted it, so listing is the only way to find the ID that cancelling needs. + +```java +List queries = client.listActiveQueries(); +for (ActiveQuery query : queries) { + System.out.printf("%s %s %s%n", + query.getQueryId(), query.getProtocol(), query.getSqlPreview()); +} + +if (!queries.isEmpty()) { + client.cancelActiveQuery(queries.get(0).getQueryId()); +} +``` + +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. 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. + +### ⏳ Async Queries + +`queryAsync()`/`queryAsyncWithParams()` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use `query()`/`queryWithParams()`. + +```java +AsyncQuery asyncQuery = client.queryAsync("SELECT * FROM taxi_trips LIMIT 10;"); + +try (ArrowReader reader = asyncQuery.results()) { // blocks until the query completes + while (reader.loadNextBatch()) { + System.out.println(reader.getVectorSchemaRoot().contentToTSVString()); + } +} +``` + +The `AsyncQuery` handle also exposes `status()` for a single poll, `waitForCompletion(Duration)` to bound how long a wait can take, and `cancel()` to request cancellation. + ## Compatibility -- Public API unchanged: both features are new methods; nothing existing was renamed, removed, or retyped. +- Public API unchanged: every feature in this release is a new method; nothing existing was renamed, removed, or retyped. - No new dependencies. From 6c2d27d7870419b2e4e36159d752a38ed6dffdab Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Fri, 21 Aug 2026 16:09:09 -0700 Subject: [PATCH 2/6] Update release notes for v0.8.0 Added documentation for asynchronous query execution and active query management features. --- docs/release_notes/v0.8.0.md | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/release_notes/v0.8.0.md b/docs/release_notes/v0.8.0.md index 6833c6e..84e1de0 100644 --- a/docs/release_notes/v0.8.0.md +++ b/docs/release_notes/v0.8.0.md @@ -29,6 +29,20 @@ System.out.println(response.getData()); String sql = client.nsqlGenerateSql(new NsqlRequest("how many trips were over 10 miles?")); ``` +### ⏳ Async Queries + +`queryAsync()`/`queryAsyncWithParams()` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use `query()`/`queryWithParams()`. + +```java +AsyncQuery asyncQuery = client.queryAsync("SELECT * FROM taxi_trips LIMIT 10;"); + +try (ArrowReader reader = asyncQuery.results()) { // blocks until the query completes + while (reader.loadNextBatch()) { + System.out.println(reader.getVectorSchemaRoot().contentToTSVString()); + } +} +``` + ### 📋 Active Query Management `listActiveQueries()` reports the synchronous queries currently running on the runtime, and `cancelActiveQuery(queryId)` cancels one. The runtime doesn't hand a query's ID back to the client that submitted it, so listing is the only way to find the ID that cancelling needs. @@ -45,22 +59,6 @@ if (!queries.isEmpty()) { } ``` -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. 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. - -### ⏳ Async Queries - -`queryAsync()`/`queryAsyncWithParams()` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use `query()`/`queryWithParams()`. - -```java -AsyncQuery asyncQuery = client.queryAsync("SELECT * FROM taxi_trips LIMIT 10;"); - -try (ArrowReader reader = asyncQuery.results()) { // blocks until the query completes - while (reader.loadNextBatch()) { - System.out.println(reader.getVectorSchemaRoot().contentToTSVString()); - } -} -``` - The `AsyncQuery` handle also exposes `status()` for a single poll, `waitForCompletion(Duration)` to bound how long a wait can take, and `cancel()` to request cancellation. ## Compatibility From b1fb75bade421d049f051693fdeb81f7d8fa38f3 Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Fri, 21 Aug 2026 16:20:25 -0700 Subject: [PATCH 3/6] 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. --- docs/release_notes/v0.8.0.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release_notes/v0.8.0.md b/docs/release_notes/v0.8.0.md index 84e1de0..1e92cdb 100644 --- a/docs/release_notes/v0.8.0.md +++ b/docs/release_notes/v0.8.0.md @@ -43,6 +43,8 @@ try (ArrowReader reader = asyncQuery.results()) { // blocks until the query comp } ``` +The `AsyncQuery` handle also exposes `status()` for a single poll, `waitForCompletion(Duration)` to bound how long a wait can take, and `cancel()` to request cancellation. + ### 📋 Active Query Management `listActiveQueries()` reports the synchronous queries currently running on the runtime, and `cancelActiveQuery(queryId)` cancels one. The runtime doesn't hand a query's ID back to the client that submitted it, so listing is the only way to find the ID that cancelling needs. @@ -59,10 +61,8 @@ if (!queries.isEmpty()) { } ``` -The `AsyncQuery` handle also exposes `status()` for a single poll, `waitForCompletion(Duration)` to bound how long a wait can take, and `cancel()` to request cancellation. - ## Compatibility -- Public API unchanged: every feature in this release is a new method; nothing existing was renamed, removed, or retyped. +- Additive, backward-compatible change: every feature in this release is a new method; nothing existing was renamed, removed, or retyped. - No new dependencies. From 27250146505986d243a1802ae21a66fe820ac46c Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Fri, 21 Aug 2026 16:38:40 -0700 Subject: [PATCH 4/6] 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. --- README.md | 20 ++++---- docs/parameterized_queries.md | 34 +++++++------- src/main/java/ai/spice/AsyncQuery.java | 4 +- src/main/java/ai/spice/Param.java | 12 ++--- src/main/java/ai/spice/QueryStatus.java | 4 +- src/main/java/ai/spice/SpiceClient.java | 47 ++++++++++++------- .../ExampleDatasetRefreshSpiceOSS.java | 2 +- .../example/ExampleIteratingResults.java | 2 +- .../example/ExampleParameterizedQueries.java | 8 ++-- .../example/ExampleSpiceCloudPlatform.java | 2 +- .../ai/spice/example/ExampleSpiceOSS.java | 2 +- src/test/java/ai/spice/AsyncQueryTest.java | 30 ++++++------ src/test/java/ai/spice/ChaosE2ETest.java | 8 ++-- .../java/ai/spice/FlightInfoReaderTest.java | 2 +- src/test/java/ai/spice/FlightQueryTest.java | 6 +-- .../java/ai/spice/LocalFlightServerTest.java | 30 ++++++------ src/test/java/ai/spice/MtlsTest.java | 8 ++-- .../java/ai/spice/ParameterizedQueryTest.java | 16 +++---- src/test/java/ai/spice/PerfBenchmarkTest.java | 8 ++-- src/test/java/ai/spice/ResetTest.java | 28 +++++------ src/test/java/ai/spice/ResilienceTest.java | 22 ++++----- src/test/java/ai/spice/SoakTest.java | 6 +-- .../java/ai/spice/StatementCacheTest.java | 4 +- .../java/ai/spice/TpchIntegrationTest.java | 38 +++++++-------- 24 files changed, 177 insertions(+), 166 deletions(-) diff --git a/README.md b/README.md index 1e30212..3cebc6f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ public class Example { try (SpiceClient client = SpiceClient.builder() .build()) { - FlightStream stream = client.query("SELECT * FROM taxi_trips LIMIT 10;"); + FlightStream stream = client.sql("SELECT * FROM taxi_trips LIMIT 10;"); while (stream.next()) { try (VectorSchemaRoot batches = stream.getRoot()) { @@ -91,7 +91,7 @@ public class Example { .withSpiceCloud() .build()) { - FlightStream stream = client.query("SELECT * FROM eth.recent_blocks LIMIT 10;"); + FlightStream stream = client.sql("SELECT * FROM eth.recent_blocks LIMIT 10;"); while (stream.next()) { try (VectorSchemaRoot batches = stream.getRoot()) { @@ -179,7 +179,7 @@ public class Example { try (SpiceClient client = SpiceClient.builder().build()) { // Query with automatic type inference - ArrowReader reader = client.queryWithParams( + ArrowReader reader = client.sqlWithParams( "SELECT * FROM taxi_trips WHERE trip_distance > $1 LIMIT 10", 5.0); // Double is inferred as Float64 @@ -201,7 +201,7 @@ public class Example { Use positional placeholders ($1, $2, etc.) for multiple parameters: ```java -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM taxi_trips WHERE trip_distance > $1 AND fare_amount > $2 LIMIT 10", 5.0, 20.0); ``` @@ -214,7 +214,7 @@ For precise control over Arrow types, use the `Param` factory methods: import ai.spice.Param; // Explicit type specification -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM orders WHERE order_id = $1 AND amount >= $2", Param.int64(12345), Param.decimal128(new BigDecimal("99.99"), 10, 2)); @@ -274,13 +274,13 @@ SpiceClient client = SpiceClient.builder() // io.grpc.StatusRuntimeException with Status.UNAVAILABLE, // SSLHandshakeException, or similar transport-level errors. try { - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { // process results... } } catch (ExecutionException e) { if (isTransportFailure(e.getCause())) { client.reset(); // discard bad transport, reconnect immediately - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { // process results with fresh connection... } } else { @@ -308,7 +308,7 @@ import org.apache.arrow.vector.VectorSchemaRoot; import org.apache.arrow.vector.types.pojo.Field; try (SpiceClient client = SpiceClient.builder().build()) { - FlightStream stream = client.query("SELECT * FROM taxi_trips LIMIT 10;"); + FlightStream stream = client.sql("SELECT * FROM taxi_trips LIMIT 10;"); while (stream.next()) { try (VectorSchemaRoot root = stream.getRoot()) { @@ -360,11 +360,11 @@ See [ExampleIteratingResults.java](/src/main/java/ai/spice/example/ExampleIterat ### Async Queries -`queryAsync`/`queryAsyncWithParams` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use [`query`](#with-locally-running-spiceai-oss)/[`queryWithParams`](#parameterized-queries-recommended). +`query`/`queryWithParams` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use [`sql`](#with-locally-running-spiceai-oss)/[`sqlWithParams`](#parameterized-queries-recommended). ```java try (SpiceClient client = SpiceClient.builder().build()) { - AsyncQuery asyncQuery = client.queryAsync("SELECT * FROM taxi_trips LIMIT 10;"); + AsyncQuery asyncQuery = client.query("SELECT * FROM taxi_trips LIMIT 10;"); try (ArrowReader reader = asyncQuery.results()) { // blocks until the query completes while (reader.loadNextBatch()) { diff --git a/docs/parameterized_queries.md b/docs/parameterized_queries.md index 84937bb..ea3e408 100644 --- a/docs/parameterized_queries.md +++ b/docs/parameterized_queries.md @@ -18,7 +18,7 @@ The parameterized query system supports three modes of parameter usage: ```java // Types are automatically inferred from Java values -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM table WHERE id = $1 AND name = $2", 42, // Inferred as Int32 "test" // Inferred as Utf8 @@ -29,7 +29,7 @@ ArrowReader reader = client.queryWithParams( ```java // Explicitly specify Arrow types for precise control -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM table WHERE id = $1 AND created = $2", Param.int32(42), Param.timestamp(LocalDateTime.now(), TimeUnit.MICROSECOND, "UTC") @@ -101,12 +101,12 @@ ArrowReader reader = client.queryWithParams( ## API Methods -### queryWithParams +### sqlWithParams Primary method for parameterized queries: ```java -public ArrowReader queryWithParams(String sql, Object... params) throws ExecutionException +public ArrowReader sqlWithParams(String sql, Object... params) throws ExecutionException ``` **Parameters:** @@ -136,7 +136,7 @@ public class Example { try (SpiceClient client = SpiceClient.builder().build()) { // Simple query with inferred types - ArrowReader reader = client.queryWithParams( + ArrowReader reader = client.sqlWithParams( "SELECT * FROM customers WHERE age > $1 AND country = $2 LIMIT $3", 18, // int -> Int32 "USA", // String -> Utf8 @@ -164,7 +164,7 @@ public class Example { try (SpiceClient client = SpiceClient.builder().build()) { // Use explicit types when precision matters - ArrowReader reader = client.queryWithParams( + ArrowReader reader = client.sqlWithParams( "SELECT * FROM orders WHERE order_id = $1 AND quantity = $2", Param.int32(12345), // Explicitly Int32 Param.int16((short) 10) // Explicitly Int16 @@ -192,7 +192,7 @@ public class Example { // Query with timestamp LocalDateTime startTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0); - ArrowReader reader = client.queryWithParams( + ArrowReader reader = client.sqlWithParams( "SELECT * FROM events WHERE created_at > $1", Param.timestamp(startTime, TimeUnit.MICROSECOND, "UTC") ); @@ -207,7 +207,7 @@ public class Example { ### Example 4: Mixed Inferred and Explicit Types ```java -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM table WHERE id = $1 AND name = $2 AND created = $3 AND active = $4", 42, // Inferred as Int32 Param.string("test"), // Explicit Utf8 @@ -224,7 +224,7 @@ import java.math.BigDecimal; // Working with high-precision decimal numbers BigDecimal amount = new BigDecimal("12345.67"); -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM transactions WHERE amount >= $1", Param.decimal128(amount, 10, 2) // 10 precision, 2 scale ); @@ -244,7 +244,7 @@ Param customParam = Param.of( new ArrowType.Timestamp(TimeUnit.NANOSECOND, "America/New_York") ); -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM table WHERE ts = $1", customParam ); @@ -252,7 +252,7 @@ ArrowReader reader = client.queryWithParams( ## Type Inference Rules -When a plain Java value is passed to `queryWithParams()`, the SDK applies these inference rules: +When a plain Java value is passed to `sqlWithParams()`, the SDK applies these inference rules: 1. **Integers**: Based on the Java type (`byte` → Int8, `short` → Int16, `int` → Int32, `long` → Int64) 2. **Floating Point**: `float` → Float32, `double` → Float64 @@ -281,7 +281,7 @@ The system provides clear error messages for type mismatches: ```java try { - ArrowReader reader = client.queryWithParams("SELECT $1", unsupportedType); + ArrowReader reader = client.sqlWithParams("SELECT $1", unsupportedType); } catch (ExecutionException e) { // Error will indicate: "Unsupported parameter type: " System.err.println("Query failed: " + e.getMessage()); @@ -309,10 +309,10 @@ Parameterized queries protect against SQL injection: // ❌ Vulnerable to SQL injection String userId = getUserInput(); // Could be: "1 OR 1=1" String sql = "SELECT * FROM users WHERE id = " + userId; -FlightStream stream = client.query(sql); +FlightStream stream = client.sql(sql); // ✅ Safe from SQL injection -ArrowReader reader = client.queryWithParams( +ArrowReader reader = client.sqlWithParams( "SELECT * FROM users WHERE id = $1", userId ); @@ -326,10 +326,10 @@ If you get type mismatch errors, use explicit types: ```java // If inference picks wrong type -ArrowReader reader = client.queryWithParams("SELECT $1", 42); // Might infer as Int32 +ArrowReader reader = client.sqlWithParams("SELECT $1", 42); // Might infer as Int32 // Use explicit type instead -ArrowReader reader = client.queryWithParams("SELECT $1", Param.int64(42)); +ArrowReader reader = client.sqlWithParams("SELECT $1", Param.int64(42)); ``` ### Unsupported Type Error @@ -338,6 +338,6 @@ If you encounter "unsupported parameter type", use `Param.of` with explicit Arro ```java Param param = Param.of(value, myCustomArrowType); -ArrowReader reader = client.queryWithParams("SELECT $1", param); +ArrowReader reader = client.sqlWithParams("SELECT $1", param); ``` diff --git a/src/main/java/ai/spice/AsyncQuery.java b/src/main/java/ai/spice/AsyncQuery.java index b54ff9f..69fdd40 100644 --- a/src/main/java/ai/spice/AsyncQuery.java +++ b/src/main/java/ai/spice/AsyncQuery.java @@ -34,8 +34,8 @@ of this software and associated documentation files (the "Software"), to deal /** * A handle to a query submitted for asynchronous execution via - * {@link SpiceClient#queryAsync(String)} or - * {@link SpiceClient#queryAsyncWithParams(String, Object...)}. + * {@link SpiceClient#query(String)} or + * {@link SpiceClient#queryWithParams(String, Object...)}. * *

* Async queries require the Spice runtime to be running in diff --git a/src/main/java/ai/spice/Param.java b/src/main/java/ai/spice/Param.java index 12bf5e5..ee7b2a8 100644 --- a/src/main/java/ai/spice/Param.java +++ b/src/main/java/ai/spice/Param.java @@ -39,20 +39,20 @@ of this software and associated documentation files (the "Software"), to deal * *

* Use the static factory methods to create parameters with explicit types, - * or pass simple Java values directly to queryWithParams for automatic type + * or pass simple Java values directly to sqlWithParams for automatic type * inference. *

- * + * *

* Example usage: *

- * + * *
  * // With type inference
- * client.queryWithParams("SELECT * FROM table WHERE id = $1", 123);
- * 
+ * client.sqlWithParams("SELECT * FROM table WHERE id = $1", 123);
+ *
  * // With explicit type
- * client.queryWithParams("SELECT * FROM table WHERE id = $1", Param.int32(123));
+ * client.sqlWithParams("SELECT * FROM table WHERE id = $1", Param.int32(123));
  * 
*/ public class Param { diff --git a/src/main/java/ai/spice/QueryStatus.java b/src/main/java/ai/spice/QueryStatus.java index 5ca7d9e..a872bfe 100644 --- a/src/main/java/ai/spice/QueryStatus.java +++ b/src/main/java/ai/spice/QueryStatus.java @@ -24,8 +24,8 @@ of this software and associated documentation files (the "Software"), to deal /** * The lifecycle status of an async query submitted via - * {@link SpiceClient#queryAsync(String)} or - * {@link SpiceClient#queryAsyncWithParams(String, Object...)}. + * {@link SpiceClient#query(String)} or + * {@link SpiceClient#queryWithParams(String, Object...)}. * *

* The runtime serializes these as plain strings such as {@code "SUCCEEDED"}. A diff --git a/src/main/java/ai/spice/SpiceClient.java b/src/main/java/ai/spice/SpiceClient.java index 03b53e5..98081e5 100644 --- a/src/main/java/ai/spice/SpiceClient.java +++ b/src/main/java/ai/spice/SpiceClient.java @@ -617,7 +617,7 @@ private FlightChannel selectChannel(FlightChannel[] snapshot) { * Resets the underlying gRPC transport by closing the current Flight channels and * cached prepared statements, then immediately establishes fresh connections with * a new DNS lookup and TLS handshake. - * This ensures the next {@link #query(String)} or {@link #queryWithParams(String, Object...)} + * This ensures the next {@link #sql(String)} or {@link #sqlWithParams(String, Object...)} * call does not incur connection setup overhead. * *

Use this method to recover from unrecoverable transport failures such as:

@@ -630,11 +630,11 @@ private FlightChannel selectChannel(FlightChannel[] snapshot) { *

Example usage for long-lived clients:

*
{@code
      * try {
-     *     return client.query(sql);
+     *     return client.sql(sql);
      * } catch (ExecutionException e) {
      *     if (isTransportFailure(e.getCause())) {
      *         client.reset();
-     *         return client.query(sql); // retry with fresh connection
+     *         return client.sql(sql); // retry with fresh connection
      *     }
      *     throw e;
      * }
@@ -807,13 +807,19 @@ && shouldRetry(((FlightRuntimeException) throwable).status()))
     }
 
     /**
-     * Executes a sql query
+     * Runs sql against the Flight endpoint and streams the results back
+     * synchronously.
+     *
+     * 

+ * Use {@link #query(String)} instead to submit sql for asynchronous + * execution on the runtime and poll for completion, which requires the + * runtime to be running in distributed/scheduler mode. * * @param sql the SQL query to execute * @return a FlightStream with the query results * @throws ExecutionException if there is an error executing the query */ - public FlightStream query(String sql) throws ExecutionException { + public FlightStream sql(String sql) throws ExecutionException { if (Strings.isNullOrEmpty(sql)) { throw new IllegalArgumentException("No SQL query provided"); } @@ -862,16 +868,21 @@ public FlightStream query(String sql) throws ExecutionException { * *

      * // With automatic type inference
-     * ArrowReader reader = client.queryWithParams(
+     * ArrowReader reader = client.sqlWithParams(
      *     "SELECT * FROM table WHERE id = $1 AND name = $2",
      *     123, "test");
      *
      * // With explicit types
-     * ArrowReader reader = client.queryWithParams(
+     * ArrowReader reader = client.sqlWithParams(
      *     "SELECT * FROM table WHERE id = $1 AND amount = $2",
      *     Param.int32(123), Param.float64(99.99));
      * 
* + *

+ * Use {@link #queryWithParams(String, Object...)} instead to submit the + * parameterized query for asynchronous execution on the runtime, which + * requires distributed/scheduler mode. + * * @param sql the SQL query with positional parameter placeholders ($1, $2, * etc.) * @param params the parameter values (can be plain values or Param instances) @@ -879,7 +890,7 @@ public FlightStream query(String sql) throws ExecutionException { * closing the reader. * @throws ExecutionException if there is an error executing the query */ - public ArrowReader queryWithParams(String sql, Object... params) throws ExecutionException { + public ArrowReader sqlWithParams(String sql, Object... params) throws ExecutionException { if (Strings.isNullOrEmpty(sql)) { throw new IllegalArgumentException("No SQL query provided"); } @@ -1696,14 +1707,14 @@ public void refreshDataset(String dataset, RefreshOptions refreshOptions) throws * cluster mode. * *

- * Use {@link #query(String)} for the normal synchronous, streaming query + * Use {@link #sql(String)} for the normal synchronous, streaming query * path. * * @param sql the SQL query to submit * @return a handle to the submitted query * @throws ExecutionException if the query could not be submitted */ - public AsyncQuery queryAsync(String sql) throws ExecutionException { + public AsyncQuery query(String sql) throws ExecutionException { return submitAsyncQuery(sql, null); } @@ -1712,11 +1723,11 @@ public AsyncQuery queryAsync(String sql) throws ExecutionException { * bound positionally ($1, $2, ...) and sent to the runtime as a JSON array, * so each parameter must be a value Gson can encode meaningfully as JSON * (numbers, strings, booleans, lists) — this bypasses the Arrow-typed - * parameter binding {@link #queryWithParams(String, Object...)} uses, so + * parameter binding {@link #sqlWithParams(String, Object...)} uses, so * temporal and decimal types are not given special handling here. * *

- * Use {@link #queryWithParams(String, Object...)} for the normal + * Use {@link #sqlWithParams(String, Object...)} for the normal * synchronous, streaming parameterized query path. * * @param sql the SQL query with positional parameter placeholders ($1, $2, @@ -1725,7 +1736,7 @@ public AsyncQuery queryAsync(String sql) throws ExecutionException { * @return a handle to the submitted query * @throws ExecutionException if the query could not be submitted */ - public AsyncQuery queryAsyncWithParams(String sql, Object... params) throws ExecutionException { + public AsyncQuery queryWithParams(String sql, Object... params) throws ExecutionException { return submitAsyncQuery(sql, (params != null && params.length > 0) ? params : null); } @@ -1828,8 +1839,8 @@ private static JsonObject parseAsyncActionResponse(byte[] body, String descripti * {@code GET /v1/sql/active}. * *

- * Synchronous queries are the ones started by {@link #query(String)}, - * {@link #queryWithParams(String, Object...)}, or issued directly over Flight + * Synchronous queries are the ones started by {@link #sql(String)}, + * {@link #sqlWithParams(String, Object...)}, or issued directly over Flight * SQL, HTTP, NSQL, or Search. The runtime does not return a query's ID to the * client that submitted it, so this is the only way to discover the ID that * {@link #cancelActiveQuery(String)} needs. @@ -2200,7 +2211,7 @@ public NsqlResponse nsql(NsqlRequest request) throws ExecutionException { * *

* Use it to inspect or edit the query before running it, or to run it - * through {@link #query(String)} or {@link #queryWithParams(String, Object...)} + * through {@link #sql(String)} or {@link #sqlWithParams(String, Object...)} * so the results arrive as Arrow rather than decoded JSON. * * @param request the natural-language query @@ -2278,8 +2289,8 @@ private FlightStream queryInternal(String sql) { .toRuntimeException(); } if (endpoints.size() > 1) { - logger.warn("Server returned {} endpoints; query() consumes only the first. " - + "Use queryWithParams() (ArrowReader) to consume all endpoints.", endpoints.size()); + logger.warn("Server returned {} endpoints; sql() consumes only the first. " + + "Use sqlWithParams() (ArrowReader) to consume all endpoints.", endpoints.size()); } Ticket ticket = endpoints.get(0).getTicket(); return channel.client.getStream(ticket, channel.streamOptions); diff --git a/src/main/java/ai/spice/example/ExampleDatasetRefreshSpiceOSS.java b/src/main/java/ai/spice/example/ExampleDatasetRefreshSpiceOSS.java index 7df6483..49fbdbd 100644 --- a/src/main/java/ai/spice/example/ExampleDatasetRefreshSpiceOSS.java +++ b/src/main/java/ai/spice/example/ExampleDatasetRefreshSpiceOSS.java @@ -49,7 +49,7 @@ public static void main(String[] args) { System.out.println("Dataset refresh triggered for taxi_trips"); System.out.println("Query taxi_trips dataset"); - FlightStream stream = client.query("SELECT * FROM taxi_trips LIMIT 1;"); + FlightStream stream = client.sql("SELECT * FROM taxi_trips LIMIT 1;"); while (stream.next()) { try (VectorSchemaRoot batches = stream.getRoot()) { diff --git a/src/main/java/ai/spice/example/ExampleIteratingResults.java b/src/main/java/ai/spice/example/ExampleIteratingResults.java index dc8cc52..c8c9d39 100644 --- a/src/main/java/ai/spice/example/ExampleIteratingResults.java +++ b/src/main/java/ai/spice/example/ExampleIteratingResults.java @@ -50,7 +50,7 @@ public class ExampleIteratingResults { public static void main(String[] args) { try (SpiceClient client = SpiceClient.builder().build()) { - FlightStream stream = client.query("SELECT * FROM taxi_trips LIMIT 5;"); + FlightStream stream = client.sql("SELECT * FROM taxi_trips LIMIT 5;"); // Process each batch of results while (stream.next()) { diff --git a/src/main/java/ai/spice/example/ExampleParameterizedQueries.java b/src/main/java/ai/spice/example/ExampleParameterizedQueries.java index a80c386..f9af699 100644 --- a/src/main/java/ai/spice/example/ExampleParameterizedQueries.java +++ b/src/main/java/ai/spice/example/ExampleParameterizedQueries.java @@ -78,7 +78,7 @@ private static void simpleParameterizedQuery(SpiceClient client) throws Exceptio // - 10.0 (Double) -> Float64 String sql = "SELECT trip_distance, fare_amount FROM taxi_trips WHERE trip_distance > $1 ORDER BY trip_distance LIMIT 5"; - try (ArrowReader reader = client.queryWithParams(sql, 10.0)) { + try (ArrowReader reader = client.sqlWithParams(sql, 10.0)) { printResults(reader); } } @@ -93,7 +93,7 @@ private static void multipleParameters(SpiceClient client) throws Exception { + "WHERE trip_distance > $1 AND fare_amount > $2 " + "ORDER BY trip_distance LIMIT 5"; - try (ArrowReader reader = client.queryWithParams(sql, 5.0, 20.0)) { + try (ArrowReader reader = client.sqlWithParams(sql, 5.0, 20.0)) { printResults(reader); } } @@ -108,7 +108,7 @@ private static void explicitParameterTypes(SpiceClient client) throws Exception + "WHERE payment_type = $1 ORDER BY trip_distance LIMIT 5"; // Use Param.int64() to explicitly specify Int64 type - try (ArrowReader reader = client.queryWithParams(sql, Param.int64(1))) { + try (ArrowReader reader = client.sqlWithParams(sql, Param.int64(1))) { printResults(reader); } } @@ -122,7 +122,7 @@ private static void mixedTypes(SpiceClient client) throws Exception { + "ORDER BY trip_distance LIMIT 5"; // Mix automatic inference (5.0) with explicit type (Param.string()) - try (ArrowReader reader = client.queryWithParams(sql, + try (ArrowReader reader = client.sqlWithParams(sql, 5.0, // Inferred as Float64 Param.string("N") // Explicit String type )) { diff --git a/src/main/java/ai/spice/example/ExampleSpiceCloudPlatform.java b/src/main/java/ai/spice/example/ExampleSpiceCloudPlatform.java index b43d3e4..525718f 100644 --- a/src/main/java/ai/spice/example/ExampleSpiceCloudPlatform.java +++ b/src/main/java/ai/spice/example/ExampleSpiceCloudPlatform.java @@ -42,7 +42,7 @@ public static void main(String[] args) { .withSpiceCloud() .build()) { - FlightStream stream = client.query("SELECT * FROM eth.recent_blocks LIMIT 10;"); + FlightStream stream = client.sql("SELECT * FROM eth.recent_blocks LIMIT 10;"); while (stream.next()) { try (VectorSchemaRoot batches = stream.getRoot()) { diff --git a/src/main/java/ai/spice/example/ExampleSpiceOSS.java b/src/main/java/ai/spice/example/ExampleSpiceOSS.java index e8953a8..88e8fe7 100644 --- a/src/main/java/ai/spice/example/ExampleSpiceOSS.java +++ b/src/main/java/ai/spice/example/ExampleSpiceOSS.java @@ -40,7 +40,7 @@ public static void main(String[] args) { try (SpiceClient client = SpiceClient.builder() .build()) { - FlightStream stream = client.query("SELECT * FROM taxi_trips LIMIT 10;"); + FlightStream stream = client.sql("SELECT * FROM taxi_trips LIMIT 10;"); while (stream.next()) { try (VectorSchemaRoot batches = stream.getRoot()) { diff --git a/src/test/java/ai/spice/AsyncQueryTest.java b/src/test/java/ai/spice/AsyncQueryTest.java index 9f16bb8..5d56588 100644 --- a/src/test/java/ai/spice/AsyncQueryTest.java +++ b/src/test/java/ai/spice/AsyncQueryTest.java @@ -40,8 +40,8 @@ of this software and associated documentation files (the "Software"), to deal import junit.framework.TestCase; /** - * Tests for {@link SpiceClient#queryAsync(String)}, - * {@link SpiceClient#queryAsyncWithParams(String, Object...)}, and + * Tests for {@link SpiceClient#query(String)}, + * {@link SpiceClient#queryWithParams(String, Object...)}, and * {@link AsyncQuery} against the in-process {@link TestFlightSqlServer}. */ public class AsyncQueryTest extends TestCase { @@ -86,7 +86,7 @@ private static byte[] serializeChunk(long idStart, String... names) throws Excep } public void testQueryAsyncSubmitsAndReturnsHandle() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); assertNotNull(query.getQueryId()); assertFalse("a fresh query id should not be blank", query.getQueryId().isEmpty()); assertEquals(QueryStatus.SUCCEEDED, query.status()); @@ -97,7 +97,7 @@ public void testQueryAsyncSubmitsAndReturnsHandle() throws Exception { } public void testQueryAsyncWithParamsSubmitsSqlAndParametersInOrder() throws Exception { - AsyncQuery query = client.queryAsyncWithParams( + AsyncQuery query = client.queryWithParams( "SELECT * FROM test WHERE id = $1 AND name = $2 AND note = $3", 1, "alice", null); assertNotNull(query.getQueryId()); @@ -112,7 +112,7 @@ public void testQueryAsyncWithParamsSubmitsSqlAndParametersInOrder() throws Exce } public void testStatusReflectsChangeBetweenPolls() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); server.setAsyncQueryStatusSequence(query.getQueryId(), Arrays.asList(QueryStatus.PENDING, QueryStatus.RUNNING, QueryStatus.SUCCEEDED)); @@ -126,7 +126,7 @@ public void testStatusReflectsChangeBetweenPolls() throws Exception { } public void testWaitForCompletionBlocksThroughPendingRunningSucceeded() throws Exception { - AsyncQuery query = client.queryAsyncWithParams("SELECT * FROM test WHERE id = $1", 1); + AsyncQuery query = client.queryWithParams("SELECT * FROM test WHERE id = $1", 1); server.setAsyncQueryStatusSequence(query.getQueryId(), Arrays.asList(QueryStatus.PENDING, QueryStatus.RUNNING, QueryStatus.RUNNING, QueryStatus.SUCCEEDED)); @@ -136,7 +136,7 @@ public void testWaitForCompletionBlocksThroughPendingRunningSucceeded() throws E } public void testWaitForCompletionTimesOut() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); // RUNNING repeated forever (the sequence holds its last entry) never reaches a terminal status. server.setAsyncQueryStatusSequence(query.getQueryId(), Arrays.asList(QueryStatus.PENDING, QueryStatus.RUNNING)); @@ -157,7 +157,7 @@ public void testWaitForCompletionTimesOut() throws Exception { } public void testResultsMultiChunkReconstructsData() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); byte[] chunk0 = serializeChunk(0, "a", "b"); byte[] chunk1 = serializeChunk(2, "c"); server.setAsyncQueryChunks(query.getQueryId(), Arrays.asList(chunk0, chunk1), 3); @@ -180,7 +180,7 @@ public void testResultsMultiChunkReconstructsData() throws Exception { } public void testResultsEmptyResultReturnsEmptySchema() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test WHERE 1 = 0"); + AsyncQuery query = client.query("SELECT * FROM test WHERE 1 = 0"); server.setAsyncQueryChunks(query.getQueryId(), Collections.emptyList(), 0); try (ArrowReader reader = query.results()) { @@ -190,7 +190,7 @@ public void testResultsEmptyResultReturnsEmptySchema() throws Exception { } public void testResultsPropagatesChunkZeroFetchFailureForDeclaredNonEmptyResult() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); byte[] chunk0 = serializeChunk(0, "a"); server.setAsyncQueryChunks(query.getQueryId(), Collections.singletonList(chunk0), 1); // A genuine one-chunk result whose chunk-0 fetch fails at the RPC level @@ -208,7 +208,7 @@ public void testResultsPropagatesChunkZeroFetchFailureForDeclaredNonEmptyResult( } public void testResultsFailedThrowsWithErrorMessage() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); server.setAsyncQueryStatusSequence(query.getQueryId(), Arrays.asList(QueryStatus.RUNNING, QueryStatus.FAILED)); server.setAsyncQueryError(query.getQueryId(), "TABLE_NOT_FOUND", "table 'test' does not exist"); @@ -223,7 +223,7 @@ public void testResultsFailedThrowsWithErrorMessage() throws Exception { } public void testCancelUpdatesStatus() throws Exception { - AsyncQuery query = client.queryAsync("SELECT * FROM test"); + AsyncQuery query = client.query("SELECT * FROM test"); server.setAsyncQueryStatusSequence(query.getQueryId(), Arrays.asList(QueryStatus.PENDING, QueryStatus.RUNNING)); @@ -234,13 +234,13 @@ public void testCancelUpdatesStatus() throws Exception { public void testQueryAsyncRejectsEmptySql() throws Exception { try { - client.queryAsync(""); + client.query(""); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected } try { - client.queryAsync(null); + client.query(null); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected @@ -250,7 +250,7 @@ public void testQueryAsyncRejectsEmptySql() throws Exception { public void testQueryAsyncWithParamsRejectsEmptySql() throws Exception { try { - client.queryAsyncWithParams("", 1); + client.queryWithParams("", 1); fail("expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected diff --git a/src/test/java/ai/spice/ChaosE2ETest.java b/src/test/java/ai/spice/ChaosE2ETest.java index ed8f3fa..8c4d0d4 100644 --- a/src/test/java/ai/spice/ChaosE2ETest.java +++ b/src/test/java/ai/spice/ChaosE2ETest.java @@ -99,7 +99,7 @@ private SpiceClient newClient(int maxRetries) throws Exception { } private static long countRows(SpiceClient client, String sql) throws Exception { - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { return LocalFlightServerTest.countRows(stream); } } @@ -136,7 +136,7 @@ public void testSurvivesRuntimeRestart() throws Exception { try (SpiceClient client = newClient(3)) { assertEquals(1, countRows(client, "SELECT 1")); // Prime the prepared-statement cache so the restart invalidates a live handle. - try (ArrowReader reader = client.queryWithParams("SELECT $1", 42L)) { + try (ArrowReader reader = client.sqlWithParams("SELECT $1", 42L)) { assertTrue(reader.loadNextBatch()); } @@ -155,7 +155,7 @@ public void testSurvivesRuntimeRestart() throws Exception { // Same client, no reset(): reconnect + re-prepare must be automatic. assertEquals(1, (long) guarded(() -> countRows(client, "SELECT 1"))); - try (ArrowReader reader = guarded(() -> client.queryWithParams("SELECT $1", 43L))) { + try (ArrowReader reader = guarded(() -> client.sqlWithParams("SELECT $1", 43L))) { assertTrue("cached statement must transparently re-prepare after restart", reader.loadNextBatch()); } @@ -227,7 +227,7 @@ public void testKillMidStreamFailsCleanlyAndRecovers() throws Exception { try { guarded(() -> { - try (FlightStream stream = client.query(bigSql)) { + try (FlightStream stream = client.sql(bigSql)) { assertTrue("stream should produce at least one batch", stream.next()); spiced.kill(); return LocalFlightServerTest.countRows(stream); diff --git a/src/test/java/ai/spice/FlightInfoReaderTest.java b/src/test/java/ai/spice/FlightInfoReaderTest.java index 1b66157..198921f 100644 --- a/src/test/java/ai/spice/FlightInfoReaderTest.java +++ b/src/test/java/ai/spice/FlightInfoReaderTest.java @@ -72,7 +72,7 @@ public void testBytesReadAccumulatesAcrossEndpoints() throws Exception { server.endpointCount = 2; server.batchesPerEndpoint = 2; try (SpiceClient client = SpiceClient.builder().withFlightAddress(server.flightUri()).build(); - org.apache.arrow.vector.ipc.ArrowReader reader = client.queryWithParams("SELECT 1", 1)) { + org.apache.arrow.vector.ipc.ArrowReader reader = client.sqlWithParams("SELECT 1", 1)) { long previous = 0; int batches = 0; while (reader.loadNextBatch()) { diff --git a/src/test/java/ai/spice/FlightQueryTest.java b/src/test/java/ai/spice/FlightQueryTest.java index 56197a5..b896a5f 100644 --- a/src/test/java/ai/spice/FlightQueryTest.java +++ b/src/test/java/ai/spice/FlightQueryTest.java @@ -52,7 +52,7 @@ public void testQuerySpiceCloudPlatform() throws ExecutionException, Interrupted int totalRows = 0; int columnCount = 0; - try (FlightStream res = spiceClient.query(sql)) { + try (FlightStream res = spiceClient.sql(sql)) { while (res.next()) { VectorSchemaRoot root = res.getRoot(); if (totalRows == 0) { @@ -85,7 +85,7 @@ public void testQuerySpiceOSS() throws ExecutionException, InterruptedException int totalRows = 0; int columnCount = 0; - try (FlightStream res = spiceClient.query(sql)) { + try (FlightStream res = spiceClient.sql(sql)) { while (res.next()) { VectorSchemaRoot root = res.getRoot(); if (totalRows == 0) { @@ -180,7 +180,7 @@ public void testRefreshWithOptionsSpiceOSS() throws ExecutionException, Interrup } private static long countRows(SpiceClient client, String sql) throws Exception { - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { long rows = 0; while (stream.next()) { rows += stream.getRoot().getRowCount(); diff --git a/src/test/java/ai/spice/LocalFlightServerTest.java b/src/test/java/ai/spice/LocalFlightServerTest.java index 6bfa6bd..609c8d5 100644 --- a/src/test/java/ai/spice/LocalFlightServerTest.java +++ b/src/test/java/ai/spice/LocalFlightServerTest.java @@ -75,7 +75,7 @@ static long countRows(ArrowReader reader) throws Exception { } public void testPlainQueryReturnsAllRows() throws Exception { - try (FlightStream stream = client.query("SELECT * FROM test")) { + try (FlightStream stream = client.sql("SELECT * FROM test")) { assertNotNull(stream.getSchema().findField("id")); assertNotNull(stream.getSchema().findField("name")); assertEquals(server.expectedTotalRows(), countRows(stream)); @@ -85,7 +85,7 @@ public void testPlainQueryReturnsAllRows() throws Exception { } public void testQueryWithParamsReturnsAllRows() throws Exception { - try (ArrowReader reader = client.queryWithParams("SELECT * FROM test WHERE id > $1", 5L)) { + try (ArrowReader reader = client.sqlWithParams("SELECT * FROM test WHERE id > $1", 5L)) { assertNotNull(reader.getVectorSchemaRoot().getSchema().findField("id")); assertEquals(server.expectedTotalRows(), countRows(reader)); assertTrue("bytesRead should be positive", reader.bytesRead() > 0); @@ -96,7 +96,7 @@ public void testQueryWithParamsReturnsAllRows() throws Exception { } public void testQueryWithParamsWithoutParameters() throws Exception { - try (ArrowReader reader = client.queryWithParams("SELECT 1")) { + try (ArrowReader reader = client.sqlWithParams("SELECT 1")) { assertEquals(server.expectedTotalRows(), countRows(reader)); } // No parameters bound: no DoPut should have happened. @@ -111,26 +111,26 @@ public void testQueryWithParamsReadsAllEndpoints() throws Exception { server.endpointCount = 3; server.batchesPerEndpoint = 2; server.rowsPerBatch = 7; - try (ArrowReader reader = client.queryWithParams("SELECT * FROM test", 1)) { + try (ArrowReader reader = client.sqlWithParams("SELECT * FROM test", 1)) { assertEquals(3 * 2 * 7, countRows(reader)); } assertEquals("one DoGet per endpoint", 3, server.doGetCalls.get()); } /** - * Documents the known limitation of the FlightStream-returning query() + * Documents the known limitation of the FlightStream-returning sql() * API: only the first endpoint of a partitioned result is consumed. */ public void testPlainQueryConsumesOnlyFirstEndpoint() throws Exception { server.endpointCount = 3; - try (FlightStream stream = client.query("SELECT * FROM test")) { + try (FlightStream stream = client.sql("SELECT * FROM test")) { assertEquals((long) server.batchesPerEndpoint * server.rowsPerBatch, countRows(stream)); } assertEquals(1, server.doGetCalls.get()); } public void testParameterValuesArriveAtServer() throws Exception { - try (ArrowReader reader = client.queryWithParams( + try (ArrowReader reader = client.sqlWithParams( "SELECT * FROM test WHERE a=$1 AND b=$2 AND c=$3 AND d=$4 AND e=$5 AND f=$6 AND g=$7 AND h=$8", 42, 42L, "hello", 3.5, true, new byte[] { 1, 2, 3 }, LocalDate.of(2026, 7, 30), new BigDecimal("12.34"))) { @@ -152,7 +152,7 @@ public void testParameterValuesArriveAtServer() throws Exception { } public void testExplicitParamTypesArriveAtServer() throws Exception { - try (ArrowReader reader = client.queryWithParams( + try (ArrowReader reader = client.sqlWithParams( "SELECT * FROM test WHERE a=$1 AND b=$2 AND c=$3", Param.int32(7), Param.string("typed"), Param.float64(2.25))) { countRows(reader); @@ -167,10 +167,10 @@ public void testExplicitParamTypesArriveAtServer() throws Exception { public void testRepeatedQueriesReturnConsistentResults() throws Exception { for (int i = 0; i < 5; i++) { - try (ArrowReader reader = client.queryWithParams("SELECT * FROM test WHERE id = $1", (long) i)) { + try (ArrowReader reader = client.sqlWithParams("SELECT * FROM test WHERE id = $1", (long) i)) { assertEquals(server.expectedTotalRows(), countRows(reader)); } - try (FlightStream stream = client.query("SELECT " + i)) { + try (FlightStream stream = client.sql("SELECT " + i)) { assertEquals(server.expectedTotalRows(), countRows(stream)); } } @@ -178,13 +178,13 @@ public void testRepeatedQueriesReturnConsistentResults() throws Exception { public void testEmptySqlThrowsIllegalArgument() throws Exception { try { - client.query(""); + client.sql(""); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected } try { - client.queryWithParams("", 1); + client.sqlWithParams("", 1); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) { // expected @@ -195,13 +195,13 @@ public void testQueryAfterCloseThrowsIllegalState() throws Exception { SpiceClient shortLived = SpiceClient.builder().withFlightAddress(server.flightUri()).build(); shortLived.close(); try { - shortLived.query("SELECT 1"); + shortLived.sql("SELECT 1"); fail("Expected IllegalStateException"); } catch (IllegalStateException expected) { assertTrue(expected.getMessage().contains("closed")); } try { - shortLived.queryWithParams("SELECT $1", 1); + shortLived.sqlWithParams("SELECT $1", 1); fail("Expected IllegalStateException"); } catch (IllegalStateException expected) { assertTrue(expected.getMessage().contains("closed")); @@ -211,7 +211,7 @@ public void testQueryAfterCloseThrowsIllegalState() throws Exception { public void testUnsupportedParameterTypeFailsWithoutRpc() throws Exception { long infoCallsBefore = server.getFlightInfoCalls.get(); try { - client.queryWithParams("SELECT $1", new Object()); + client.sqlWithParams("SELECT $1", new Object()); fail("Expected ExecutionException"); } catch (ExecutionException e) { assertTrue("cause should be IllegalArgumentException, got: " + e.getCause(), diff --git a/src/test/java/ai/spice/MtlsTest.java b/src/test/java/ai/spice/MtlsTest.java index a3fc7e6..a205343 100644 --- a/src/test/java/ai/spice/MtlsTest.java +++ b/src/test/java/ai/spice/MtlsTest.java @@ -61,12 +61,12 @@ private static SpiceClientBuilder clientFor(TestFlightSqlServer server) throws E } private static void assertQueriesWork(SpiceClient client, TestFlightSqlServer server) throws Exception { - try (FlightStream stream = client.query("SELECT * FROM test")) { + try (FlightStream stream = client.sql("SELECT * FROM test")) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(stream)); } // Parameterized queries run on the same TLS channel (prepared // statements inherit the transport configuration). - try (ArrowReader reader = client.queryWithParams("SELECT * FROM test WHERE id > $1", 1L)) { + try (ArrowReader reader = client.sqlWithParams("SELECT * FROM test WHERE id > $1", 1L)) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(reader)); } } @@ -100,7 +100,7 @@ public void testMissingClientCertificateRejected() throws Exception { .withTlsRootCertFile(certs.caCert.toString()) .build()) { try { - client.query("SELECT 1"); + client.sql("SELECT 1"); fail("Expected the TLS handshake to be rejected without a client certificate"); } catch (ExecutionException e) { assertTlsFailure(e); @@ -115,7 +115,7 @@ public void testUntrustedServerCaRejected() throws Exception { .withTlsRootCertFile(certs.otherCaCert.toString()) .build()) { try { - client.query("SELECT 1"); + client.sql("SELECT 1"); fail("Expected certificate verification to fail against an untrusted CA"); } catch (ExecutionException e) { assertTlsFailure(e); diff --git a/src/test/java/ai/spice/ParameterizedQueryTest.java b/src/test/java/ai/spice/ParameterizedQueryTest.java index be65400..dd30f37 100644 --- a/src/test/java/ai/spice/ParameterizedQueryTest.java +++ b/src/test/java/ai/spice/ParameterizedQueryTest.java @@ -60,7 +60,7 @@ public void testParameterizedQuerySpiceCloud() throws Exception { // Test with float parameter - taxi_trips available in Spice Cloud String sql = "SELECT tpep_pickup_datetime, total_amount FROM taxi_trips WHERE total_amount > $1 ORDER BY total_amount LIMIT 5"; - try (ArrowReader reader = spiceClient.queryWithParams(sql, 10.0)) { + try (ArrowReader reader = spiceClient.sqlWithParams(sql, 10.0)) { int totalRows = 0; while (reader.loadNextBatch()) { @@ -86,7 +86,7 @@ public void testParameterizedQuerySpiceOSS() throws Exception { // Test with float parameter on tpch.orders String sql = "SELECT o_orderkey, o_totalprice FROM tpch.orders WHERE o_totalprice > $1 ORDER BY o_totalprice LIMIT 5"; - try (ArrowReader reader = spiceClient.queryWithParams(sql, 10000.0)) { + try (ArrowReader reader = spiceClient.sqlWithParams(sql, 10000.0)) { int totalRows = 0; while (reader.loadNextBatch()) { @@ -116,7 +116,7 @@ public void testMultipleParameters() throws Exception { try (SpiceClient spiceClient = SpiceClient.builder().withMaxRetries(1).build()) { String sql = "SELECT o_orderkey, o_totalprice FROM tpch.orders WHERE o_totalprice > $1 AND o_custkey > $2 LIMIT 5"; - try (ArrowReader reader = spiceClient.queryWithParams(sql, 5000.0, 100)) { + try (ArrowReader reader = spiceClient.sqlWithParams(sql, 5000.0, 100)) { int totalRows = 0; while (reader.loadNextBatch()) { @@ -144,7 +144,7 @@ public void testStringParameter() throws Exception { // Use c_mktsegment which is a string column in tpch.customer String sql = "SELECT c_custkey, c_mktsegment FROM tpch.customer WHERE c_mktsegment = $1 LIMIT 5"; - try (ArrowReader reader = spiceClient.queryWithParams(sql, "BUILDING")) { + try (ArrowReader reader = spiceClient.sqlWithParams(sql, "BUILDING")) { int totalRows = 0; while (reader.loadNextBatch()) { @@ -172,7 +172,7 @@ public void testExplicitParamTypes() throws Exception { // Use explicit int64 type on tpch.customer String sql = "SELECT c_custkey, c_name, c_nationkey FROM tpch.customer WHERE c_nationkey = $1 LIMIT 5"; - try (ArrowReader reader = spiceClient.queryWithParams(sql, Param.int64(1))) { + try (ArrowReader reader = spiceClient.sqlWithParams(sql, Param.int64(1))) { int totalRows = 0; while (reader.loadNextBatch()) { @@ -198,7 +198,7 @@ public void testMixedParameterTypes() throws Exception { try (SpiceClient spiceClient = SpiceClient.builder().withMaxRetries(1).build()) { String sql = "SELECT o_orderkey, o_totalprice FROM tpch.orders WHERE o_totalprice > $1 AND o_orderstatus = $2 LIMIT 5"; - try (ArrowReader reader = spiceClient.queryWithParams(sql, + try (ArrowReader reader = spiceClient.sqlWithParams(sql, Param.float64(5000.0), Param.string("O"))) { int totalRows = 0; @@ -292,7 +292,7 @@ public void testParamFactoryMethods() { public void testNullSqlThrows() throws Exception { try (SpiceClient spiceClient = SpiceClient.builder().withMaxRetries(1).build()) { try { - spiceClient.queryWithParams(null, 1); + spiceClient.sqlWithParams(null, 1); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("No SQL query provided")); @@ -312,7 +312,7 @@ public void testNullSqlThrows() throws Exception { public void testEmptySqlThrows() throws Exception { try (SpiceClient spiceClient = SpiceClient.builder().withMaxRetries(1).build()) { try { - spiceClient.queryWithParams("", 1); + spiceClient.sqlWithParams("", 1); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("No SQL query provided")); diff --git a/src/test/java/ai/spice/PerfBenchmarkTest.java b/src/test/java/ai/spice/PerfBenchmarkTest.java index d69876e..1a07c33 100644 --- a/src/test/java/ai/spice/PerfBenchmarkTest.java +++ b/src/test/java/ai/spice/PerfBenchmarkTest.java @@ -128,7 +128,7 @@ private static long checkedCountRows(ArrowReader reader, long expectedRows) thro private static Callable paramsOp(SpiceClient client, long expectedRows) { return () -> { - try (ArrowReader reader = client.queryWithParams(SQL, 5L)) { + try (ArrowReader reader = client.sqlWithParams(SQL, 5L)) { return checkedCountRows(reader, expectedRows); } }; @@ -138,7 +138,7 @@ public void testBenchmarkPlainQuery() throws Exception { try (SpiceClient client = SpiceClient.builder().withFlightAddress(server.flightUri()).build()) { final long expectedRows = server.expectedTotalRows(); Callable op = () -> { - try (FlightStream stream = client.query("SELECT * FROM bench")) { + try (FlightStream stream = client.sql("SELECT * FROM bench")) { return checkedCountRows(stream, expectedRows); } }; @@ -146,8 +146,8 @@ public void testBenchmarkPlainQuery() throws Exception { long getFlightInfoBefore = server.getFlightInfoCalls.get(); long doGetBefore = server.doGetCalls.get(); long[] samples = measure(MEASURED_ITERATIONS, op); - System.out.println("[bench] " + stats("query()", samples)); - recordBench("query() p50", "us", p50Micros(samples)); + System.out.println("[bench] " + stats("sql()", samples)); + recordBench("sql() p50", "us", p50Micros(samples)); // The plain query path is exactly 2 RPCs: GetFlightInfo + DoGet. assertEquals(MEASURED_ITERATIONS, server.getFlightInfoCalls.get() - getFlightInfoBefore); diff --git a/src/test/java/ai/spice/ResetTest.java b/src/test/java/ai/spice/ResetTest.java index 6127f77..cc1daf6 100644 --- a/src/test/java/ai/spice/ResetTest.java +++ b/src/test/java/ai/spice/ResetTest.java @@ -57,7 +57,7 @@ protected void setUp() throws Exception { // Probe with taxi_trips (not SELECT 1) to ensure // the dataset is loaded and ready, not just that // the server is up. - try (FlightStream stream = probe.query( + try (FlightStream stream = probe.sql( "SELECT total_amount FROM taxi_trips LIMIT 1")) { stream.next(); } @@ -126,7 +126,7 @@ public void testQueryAfterResetRebuildsClient() throws Exception { client.reset(); try { - try (FlightStream stream = client.query("SELECT 1")) { + try (FlightStream stream = client.sql("SELECT 1")) { // If a local Spice runtime is running, this succeeds stream.next(); } @@ -154,7 +154,7 @@ public void testQueryWithParamsAfterResetRebuildsClient() throws Exception { client.reset(); try { - try (ArrowReader reader = client.queryWithParams("SELECT $1", 42)) { + try (ArrowReader reader = client.sqlWithParams("SELECT $1", 42)) { while (reader.loadNextBatch()) { // consume } @@ -190,7 +190,7 @@ public void testResetQueryResetQueryCycle() throws Exception { for (int i = 0; i < 3; i++) { client.reset(); try { - FlightStream stream = client.query("SELECT 1"); + FlightStream stream = client.sql("SELECT 1"); stream.close(); } catch (Exception e) { // Connection errors are fine — we're testing the reset/rebuild cycle, @@ -266,8 +266,8 @@ public void testConcurrentResetDoesNotThrow() throws Exception { } /** - * Concurrent reset() and query() should not throw unexpected errors. - * (query may fail with connection errors, but not NPE or IllegalStateException.) + * Concurrent reset() and sql() should not throw unexpected errors. + * (sql may fail with connection errors, but not NPE or IllegalStateException.) */ public void testConcurrentResetAndQuery() throws Exception { final SpiceClient client = SpiceClient.builder().withMaxRetries(0).build(); @@ -295,7 +295,7 @@ public void testConcurrentResetAndQuery() throws Exception { startLatch.await(); for (int i = 0; i < iterations; i++) { try { - FlightStream stream = client.query("SELECT 1"); + FlightStream stream = client.sql("SELECT 1"); stream.close(); } catch (Exception e) { // Unwrap ExecutionException to inspect the real cause @@ -440,7 +440,7 @@ public void testResetWithCustomConfig() throws Exception { client.reset(); try { - FlightStream stream = client.query("SELECT 1"); + FlightStream stream = client.sql("SELECT 1"); stream.close(); } catch (Exception e) { assertFalse("NPE after reset with custom config", @@ -454,7 +454,7 @@ public void testResetWithCustomConfig() throws Exception { /** * If a local Spice runtime is running, verify that - * reset() followed by query() actually returns data. + * reset() followed by sql() actually returns data. * Uses taxi_trips which is available in the CI quickstart dataset. */ public void testResetThenQueryIntegration() throws Exception { @@ -462,7 +462,7 @@ public void testResetThenQueryIntegration() throws Exception { try (SpiceClient client = SpiceClient.builder().build()) { // First query (establishes connection) - try (FlightStream stream1 = client.query( + try (FlightStream stream1 = client.sql( "SELECT total_amount FROM taxi_trips LIMIT 1")) { int rows1 = 0; while (stream1.next()) { @@ -475,7 +475,7 @@ public void testResetThenQueryIntegration() throws Exception { client.reset(); // Second query (lazy rebuild) - try (FlightStream stream2 = client.query( + try (FlightStream stream2 = client.sql( "SELECT total_amount FROM taxi_trips LIMIT 2")) { int rows2 = 0; while (stream2.next()) { @@ -488,7 +488,7 @@ public void testResetThenQueryIntegration() throws Exception { /** * If a local Spice runtime is running, verify that - * reset() followed by queryWithParams() actually returns data. + * reset() followed by sqlWithParams() actually returns data. * Uses taxi_trips which is available in the CI quickstart dataset. */ public void testResetThenQueryWithParamsIntegration() throws Exception { @@ -496,7 +496,7 @@ public void testResetThenQueryWithParamsIntegration() throws Exception { try (SpiceClient client = SpiceClient.builder().build()) { // First query - try (ArrowReader reader1 = client.queryWithParams( + try (ArrowReader reader1 = client.sqlWithParams( "SELECT total_amount FROM taxi_trips WHERE total_amount > $1 LIMIT 1", 0.0)) { int rows1 = 0; @@ -510,7 +510,7 @@ public void testResetThenQueryWithParamsIntegration() throws Exception { client.reset(); // Second query (rebuilds channels and re-prepares the statement) - try (ArrowReader reader2 = client.queryWithParams( + try (ArrowReader reader2 = client.sqlWithParams( "SELECT total_amount FROM taxi_trips WHERE total_amount > $1 LIMIT 2", 0.0)) { int rows2 = 0; diff --git a/src/test/java/ai/spice/ResilienceTest.java b/src/test/java/ai/spice/ResilienceTest.java index 8e87c3a..8cdde3b 100644 --- a/src/test/java/ai/spice/ResilienceTest.java +++ b/src/test/java/ai/spice/ResilienceTest.java @@ -57,7 +57,7 @@ public void testTransientUnavailableIsRetriedWithBackoff() throws Exception { server.failNextGetFlightInfo(1, CallStatus.UNAVAILABLE); long startNanos = System.nanoTime(); - try (FlightStream stream = client.query("SELECT 1")) { + try (FlightStream stream = client.sql("SELECT 1")) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(stream)); } long elapsedMs = (System.nanoTime() - startNanos) / 1_000_000; @@ -76,7 +76,7 @@ public void testRetriesExhaustedSurfaceLastError() throws Exception { .build()) { server.failNextGetFlightInfo(10, CallStatus.UNAVAILABLE); try { - client.query("SELECT 1"); + client.sql("SELECT 1"); fail("Expected ExecutionException"); } catch (ExecutionException e) { assertTrue(e.getCause() instanceof FlightRuntimeException); @@ -98,7 +98,7 @@ public void testNonRetryableErrorFailsFast() throws Exception { long startNanos = System.nanoTime(); try { - client.query("SELECT invalid"); + client.sql("SELECT invalid"); fail("Expected ExecutionException"); } catch (ExecutionException e) { assertEquals(FlightStatusCode.INVALID_ARGUMENT, @@ -126,7 +126,7 @@ public void testQueryTimeoutBoundsPlanning() throws Exception { long startNanos = System.nanoTime(); try { - client.query("SELECT 1"); + client.sql("SELECT 1"); fail("Expected ExecutionException"); } catch (ExecutionException e) { assertEquals(FlightStatusCode.TIMED_OUT, @@ -151,13 +151,13 @@ public void testExpiredBearerTokenRecoversAutomatically() throws Exception { .build()) { assertEquals("constructor performs the initial handshake", 1, server.basicAuthValidations.get()); - try (FlightStream stream = client.query("SELECT 1")) { + try (FlightStream stream = client.sql("SELECT 1")) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(stream)); } server.rejectNextBearerToken(); - try (FlightStream stream = client.query("SELECT 2")) { + try (FlightStream stream = client.sql("SELECT 2")) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(stream)); } assertEquals("expired token must trigger exactly one re-handshake", 2, @@ -176,7 +176,7 @@ public void testQueryWithParamsOnAuthenticatedClient() throws Exception { .withFlightAddress(server.flightUri()) .withApiKey("testapp|secret") .build()) { - try (ArrowReader reader = client.queryWithParams("SELECT * FROM t WHERE id=$1", 7L)) { + try (ArrowReader reader = client.sqlWithParams("SELECT * FROM t WHERE id=$1", 7L)) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(reader)); } assertEquals("no extra handshake beyond the constructor's", 1, @@ -191,18 +191,18 @@ public void testChannelPoolServesQueries() throws Exception { .withChannelCount(4) .build()) { for (int i = 0; i < 8; i++) { - try (FlightStream stream = client.query("SELECT " + i)) { + try (FlightStream stream = client.sql("SELECT " + i)) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(stream)); } } - try (ArrowReader reader = client.queryWithParams("SELECT * FROM t WHERE id=$1", 1L)) { + try (ArrowReader reader = client.sqlWithParams("SELECT * FROM t WHERE id=$1", 1L)) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(reader)); } assertEquals(9, server.getFlightInfoCalls.get()); // reset() rebuilds all channels and queries still work. client.reset(); - try (FlightStream stream = client.query("SELECT after_reset")) { + try (FlightStream stream = client.sql("SELECT after_reset")) { assertEquals(server.expectedTotalRows(), LocalFlightServerTest.countRows(stream)); } } @@ -238,7 +238,7 @@ public void testConcurrentResetAndQueryWithParams() throws Exception { try { start.await(); for (int i = 0; i < queries; i++) { - try (ArrowReader reader = client.queryWithParams( + try (ArrowReader reader = client.sqlWithParams( "SELECT * FROM t WHERE id=$1", (long) i)) { LocalFlightServerTest.countRows(reader); } diff --git a/src/test/java/ai/spice/SoakTest.java b/src/test/java/ai/spice/SoakTest.java index d73d606..fa2bac0 100644 --- a/src/test/java/ai/spice/SoakTest.java +++ b/src/test/java/ai/spice/SoakTest.java @@ -86,7 +86,7 @@ public void testSoak() throws Exception { try (SpiceClient client = SpiceClient.builder().withMaxRetries(3).build()) { // Fail fast (before the long run) if the runtime isn't serving. assertTrue("runtime must be ready before soaking", client.isReady()); - try (FlightStream warm = client.query(querySql)) { + try (FlightStream warm = client.sql(querySql)) { LocalFlightServerTest.countRows(warm); } // Baseline AFTER warm-up: gRPC/Netty event loops and the JDK HTTP @@ -105,12 +105,12 @@ public void testSoak() throws Exception { try { int kind = roll++ % 20; if (kind < 16) { - try (FlightStream stream = client.query(querySql)) { + try (FlightStream stream = client.sql(querySql)) { rowsRead.addAndGet(LocalFlightServerTest.countRows(stream)); } dataOperations.incrementAndGet(); } else if (kind < 19) { - try (ArrowReader reader = client.queryWithParams(paramSql, 1L)) { + try (ArrowReader reader = client.sqlWithParams(paramSql, 1L)) { rowsRead.addAndGet(LocalFlightServerTest.countRows(reader)); } dataOperations.incrementAndGet(); diff --git a/src/test/java/ai/spice/StatementCacheTest.java b/src/test/java/ai/spice/StatementCacheTest.java index 14a521d..9027fb6 100644 --- a/src/test/java/ai/spice/StatementCacheTest.java +++ b/src/test/java/ai/spice/StatementCacheTest.java @@ -61,7 +61,7 @@ private SpiceClient newClient() throws Exception { } private static void runQuery(SpiceClient client, String sql, Object... params) throws Exception { - try (ArrowReader reader = client.queryWithParams(sql, params)) { + try (ArrowReader reader = client.sqlWithParams(sql, params)) { long rows = LocalFlightServerTest.countRows(reader); assertTrue("query should return rows", rows > 0); } @@ -171,7 +171,7 @@ public void testConcurrentSameSqlQueries() throws Exception { try { start.await(); for (int i = 0; i < queriesPerThread; i++) { - try (ArrowReader reader = client.queryWithParams(SQL, (long) i)) { + try (ArrowReader reader = client.sqlWithParams(SQL, (long) i)) { totalRows.addAndGet(LocalFlightServerTest.countRows(reader)); } } diff --git a/src/test/java/ai/spice/TpchIntegrationTest.java b/src/test/java/ai/spice/TpchIntegrationTest.java index 52f3b85..9eaf41c 100644 --- a/src/test/java/ai/spice/TpchIntegrationTest.java +++ b/src/test/java/ai/spice/TpchIntegrationTest.java @@ -59,7 +59,7 @@ protected void setUp() throws Exception { synchronized (TpchIntegrationTest.class) { if (tpchAvailableCached == null) { try (SpiceClient probe = SpiceClient.builder().withMaxRetries(1).build(); - FlightStream stream = probe.query("SELECT c_custkey FROM tpch.customer LIMIT 1")) { + FlightStream stream = probe.sql("SELECT c_custkey FROM tpch.customer LIMIT 1")) { stream.next(); tpchAvailableCached = Boolean.TRUE; } catch (Exception e) { @@ -88,7 +88,7 @@ protected void tearDown() throws Exception { public void testShowTables() throws Exception { if (!tpchAvailable) return; - try (FlightStream stream = client.query("SHOW TABLES")) { + try (FlightStream stream = client.sql("SHOW TABLES")) { Set tableNames = new HashSet<>(); int columnCount = 0; @@ -121,7 +121,7 @@ public void testCustomerQuery() throws Exception { if (!tpchAvailable) return; String sql = "SELECT c_custkey, c_name, c_nationkey FROM tpch.customer LIMIT 10"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; boolean hasExpectedColumns = false; @@ -156,7 +156,7 @@ public void testCustomerWithFilter() throws Exception { if (!tpchAvailable) return; String sql = "SELECT c_custkey, c_nationkey FROM tpch.customer WHERE c_nationkey = 1 LIMIT 5"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -181,7 +181,7 @@ public void testOrdersQuery() throws Exception { if (!tpchAvailable) return; String sql = "SELECT o_orderkey, o_custkey, o_totalprice FROM tpch.orders LIMIT 10"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -208,7 +208,7 @@ public void testOrdersWithPriceFilter() throws Exception { // Use a lower threshold that works with any scale factor String sql = "SELECT o_orderkey, o_totalprice FROM tpch.orders WHERE o_totalprice > 1000 LIMIT 5"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -233,7 +233,7 @@ public void testCountQuery() throws Exception { if (!tpchAvailable) return; String sql = "SELECT COUNT(*) as cnt FROM tpch.customer"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { long count = 0; while (stream.next()) { @@ -252,7 +252,7 @@ public void testSumQuery() throws Exception { // Use direct SUM without LIMIT (LIMIT doesn't work on aggregation) String sql = "SELECT SUM(o_totalprice) as total FROM tpch.orders"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { Double sum = null; while (stream.next()) { @@ -274,7 +274,7 @@ public void testGroupByQuery() throws Exception { if (!tpchAvailable) return; String sql = "SELECT o_orderstatus, COUNT(*) as cnt FROM tpch.orders GROUP BY o_orderstatus"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int statusCount = 0; while (stream.next()) { @@ -300,7 +300,7 @@ public void testSimpleJoin() throws Exception { "FROM tpch.customer c " + "JOIN tpch.orders o ON c.c_custkey = o.o_custkey " + "LIMIT 5"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -321,7 +321,7 @@ public void testDescribeTable() throws Exception { if (!tpchAvailable) return; String sql = "DESCRIBE tpch.customer"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int columnCount = 0; Set columnNames = new HashSet<>(); @@ -353,7 +353,7 @@ public void testOrderByAsc() throws Exception { if (!tpchAvailable) return; String sql = "SELECT c_custkey FROM tpch.customer ORDER BY c_custkey ASC LIMIT 5"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { long previousKey = -1; while (stream.next()) { @@ -373,7 +373,7 @@ public void testOrderByDesc() throws Exception { if (!tpchAvailable) return; String sql = "SELECT c_custkey FROM tpch.customer ORDER BY c_custkey DESC LIMIT 5"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { long previousKey = Long.MAX_VALUE; while (stream.next()) { @@ -396,7 +396,7 @@ public void testNullHandling() throws Exception { // Query that might return nulls String sql = "SELECT c_custkey, c_phone FROM tpch.customer LIMIT 10"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -426,7 +426,7 @@ public void testLargeResultSet() throws Exception { if (!tpchAvailable) return; String sql = "SELECT c_custkey, c_name FROM tpch.customer LIMIT 1000"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -445,7 +445,7 @@ public void testEmptyResult() throws Exception { // Query that should return no results String sql = "SELECT c_custkey FROM tpch.customer WHERE c_custkey < 0"; - try (FlightStream stream = client.query(sql)) { + try (FlightStream stream = client.sql(sql)) { int totalRows = 0; while (stream.next()) { @@ -463,7 +463,7 @@ public void testInvalidTableName() { if (!tpchAvailable) return; try { - try (FlightStream stream = client.query("SELECT * FROM nonexistent_table")) { + try (FlightStream stream = client.sql("SELECT * FROM nonexistent_table")) { while (stream.next()) { // Should not get here } @@ -482,7 +482,7 @@ public void testInvalidColumnName() { if (!tpchAvailable) return; try { - try (FlightStream stream = client.query("SELECT nonexistent_column FROM tpch.customer")) { + try (FlightStream stream = client.sql("SELECT nonexistent_column FROM tpch.customer")) { while (stream.next()) { // Should not get here } @@ -501,7 +501,7 @@ public void testSyntaxError() { if (!tpchAvailable) return; try { - try (FlightStream stream = client.query("SELEC * FROM tpch.customer")) { + try (FlightStream stream = client.sql("SELEC * FROM tpch.customer")) { while (stream.next()) { // Should not get here } From 48c9c8ed1045b088f51f61516598399261a24f55 Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Fri, 21 Aug 2026 16:43:33 -0700 Subject: [PATCH 5/6] 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(). --- docs/release_notes/v0.8.0.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/docs/release_notes/v0.8.0.md b/docs/release_notes/v0.8.0.md index 1e92cdb..78c2022 100644 --- a/docs/release_notes/v0.8.0.md +++ b/docs/release_notes/v0.8.0.md @@ -19,7 +19,7 @@ for (SearchMatch match : response.getResults()) { ### 🗣️ Natural Language to SQL (Nsql) -`nsql()` translates a natural-language query into SQL via the runtime's configured LLM and runs it, returning the rows alongside the generated SQL. `nsqlGenerateSql()` generates the SQL without running it — inspect or edit it, or run it through `query()`/`queryWithParams()` for Arrow-typed results instead of `nsql()`'s decoded JSON rows. +`nsql()` translates a natural-language query into SQL via the runtime's configured LLM and runs it, returning the rows alongside the generated SQL. `nsqlGenerateSql()` generates the SQL without running it — inspect or edit it, or run it through `sql()`/`sqlWithParams()` for Arrow-typed results instead of `nsql()`'s decoded JSON rows. ```java NsqlResponse response = client.nsql(new NsqlRequest("how many trips were over 10 miles?")); @@ -31,10 +31,10 @@ String sql = client.nsqlGenerateSql(new NsqlRequest("how many trips were over 10 ### ⏳ Async Queries -`queryAsync()`/`queryAsyncWithParams()` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use `query()`/`queryWithParams()`. +`query()`/`queryWithParams()` submit a query for asynchronous execution and return an `AsyncQuery` handle instead of streaming results directly. This requires the Spice runtime to be running in distributed/scheduler mode; for the normal synchronous, streaming path use `sql()`/`sqlWithParams()`. ```java -AsyncQuery asyncQuery = client.queryAsync("SELECT * FROM taxi_trips LIMIT 10;"); +AsyncQuery asyncQuery = client.query("SELECT * FROM taxi_trips LIMIT 10;"); try (ArrowReader reader = asyncQuery.results()) { // blocks until the query completes while (reader.loadNextBatch()) { @@ -61,8 +61,23 @@ if (!queries.isEmpty()) { } ``` +## Breaking Changes + +**`query()` and `queryWithParams()` now submit SQL for asynchronous execution and return an `AsyncQuery` handle**, instead of streaming results directly. The previous synchronous, streaming behavior is now `sql()`/`sqlWithParams()`: + +```java +// Before (v0.7.0 and earlier) +FlightStream stream = client.query("SELECT * FROM taxi_trips"); + +// After (v0.8.0) +FlightStream stream = client.sql("SELECT * FROM taxi_trips"); +``` + +Async queries additionally require the runtime to be running in distributed/scheduler mode; calling `query()`/`queryWithParams()` against a single-node runtime now returns an error explaining that, rather than the query results. + +Everything else in this release is additive: `search()`, `nsql()`/`nsqlGenerateSql()`, and `listActiveQueries()`/`cancelActiveQuery()` are all new methods; nothing else existing was renamed, removed, or retyped. + ## Compatibility -- Additive, backward-compatible change: every feature in this release is a new method; nothing existing was renamed, removed, or retyped. - No new dependencies. From 1e5d13148f102f70cacd0bd9c467666f37acf65e Mon Sep 17 00:00:00 2001 From: Viktor Yershov Date: Fri, 21 Aug 2026 17:04:19 -0700 Subject: [PATCH 6/6] 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. --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index 88d2bee..69f790c 100644 --- a/pom.xml +++ b/pom.xml @@ -265,6 +265,13 @@ ai.spice.example + + ai.spice.SpiceClient#query(java.lang.String) + ai.spice.SpiceClient#queryWithParams(java.lang.String, java.lang.Object[])