Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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

Expand All @@ -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);
```
Expand All @@ -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));
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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()) {
Expand Down
34 changes: 17 additions & 17 deletions docs/parameterized_queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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:**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
);
Expand All @@ -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
Expand All @@ -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
);
Expand All @@ -244,15 +244,15 @@ 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
);
```

## 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
Expand Down Expand Up @@ -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: <type>"
System.err.println("Query failed: " + e.getMessage());
Expand Down Expand Up @@ -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
);
Expand All @@ -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
Expand All @@ -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);
```

51 changes: 49 additions & 2 deletions docs/release_notes/v0.8.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?"));
Expand All @@ -29,8 +29,55 @@ System.out.println(response.getData());
String sql = client.nsqlGenerateSql(new NsqlRequest("how many trips were over 10 miles?"));
```

### ⏳ Async Queries

`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.query("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.

### 📋 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<ActiveQuery> 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());
}
```

## 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

- Public API unchanged: both features are new methods; nothing existing was renamed, removed, or retyped.
- No new dependencies.

7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,13 @@
<!-- Examples ship in the jar but are not a compatibility surface. -->
<excludes>
<exclude>ai.spice.example</exclude>
<!-- v0.8.0's intentional breaking change: query()/queryWithParams() now
submit for asynchronous execution and return AsyncQuery, instead of
streaming results directly. The previous behavior moved to the new
sql()/sqlWithParams() methods. Remove these two exclusions once
japicmp.oldVersion is bumped past 0.8.0. -->
<exclude>ai.spice.SpiceClient#query(java.lang.String)</exclude>
<exclude>ai.spice.SpiceClient#queryWithParams(java.lang.String, java.lang.Object[])</exclude>
Comment thread
krinart marked this conversation as resolved.
</excludes>
<!-- v0.6.0 internals referenced ADBC, which 0.7.0 removed
from the classpath; those types were never public API. -->
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ai/spice/AsyncQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -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...)}.
*
* <p>
* Async queries require the Spice runtime to be running in
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/ai/spice/Param.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,20 @@ of this software and associated documentation files (the "Software"), to deal
*
* <p>
* 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.
* </p>
*
*
* <p>
* Example usage:
* </p>
*
*
* <pre>
* // 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));
* </pre>
*/
public class Param {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ai/spice/QueryStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -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...)}.
*
* <p>
* The runtime serializes these as plain strings such as {@code "SUCCEEDED"}. A
Expand Down
Loading
Loading