Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## [Unreleased]
- Add filtered live queries for parity with the JS SDK's `live(table).where(expr)`: `Surreal.selectLive(String table, String where)` appends a raw SurrealQL condition to the generated `LIVE SELECT * FROM type::table($tb)` statement (same trust model as `query()`), and `Surreal.selectLive(String table, String where, Map<String, ?> params)` additionally binds parameters — the safe way to inject condition values (e.g. `"age > $minAge"`; the binding name `tb` is reserved). Both keep the unfiltered variant's behavior: eager subscription-error surfacing (including an invalid WHERE clause), the live-query UUID available up front, and `kill()`/`close()` semantics [#194](https://github.com/surrealdb/surrealdb.java/pull/194).

## [2.1.2] - 2026-06-24
- Add full geometry-type support for reading and writing all seven types — Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and GeometryCollection — via `Geometry` type-discrimination accessors (`getType()`, `isPolygon()`, …), readers returning `java.awt.geom.Point2D.Double` coordinates (x = longitude, y = latitude), and factory methods that serialize through `create`/`update` content and bound parameters. Also fixes an `UnsatisfiedLinkError` thrown when a `Geometry` was finalized (the native `deleteInstance` had no matching Rust symbol) [#183](https://github.com/surrealdb/surrealdb.java/pull/183).
- Add `Surreal.kill(String)` / `Surreal.kill(java.util.UUID)` to terminate a live query by id, and `LiveStream.getQueryId()` to read the live-query UUID immediately, before the first notification; `selectLive` now starts the subscription through the public `LIVE SELECT` query path so the id is available up front. `kill()` stops notifications but does not close a local `LiveStream` — use `LiveStream.close()` to release a blocked `next()` [#184](https://github.com/surrealdb/surrealdb.java/pull/184).
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/surrealdb/LiveStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

/**
* Blocking iterator over live query notifications returned by
* {@link Surreal#selectLive(String)}.
* {@link Surreal#selectLive(String)} and its filtered
* {@link Surreal#selectLive(String, String, java.util.Map)} overloads.
*
* <p>
* Typical usage:
Expand Down
95 changes: 95 additions & 0 deletions src/main/java/com/surrealdb/Surreal.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ private static native long upsertRecordIdRangeValue(long ptr, String table, long

private static native LiveStream selectLive(long ptr, String table);

private static native LiveStream selectLiveWhere(long ptr, String table, String where, String[] paramsKey,
long[] valuePtrs);

private static native void kill(long ptr, String queryId);

@Override
Expand Down Expand Up @@ -289,6 +292,98 @@ public LiveStream selectLive(String table) {
return selectLive(getPtr(), table);
}

/**
* Starts a filtered live query on the given table and returns a blocking stream
* of notifications (CREATE, UPDATE, DELETE) for the records that match the
* given {@code WHERE} condition. This mirrors the JS SDK's
* {@code live(table).where(expr)}.
*
* <p>
* The {@code where} argument is a raw SurrealQL expression appended verbatim to
* the generated {@code LIVE SELECT * FROM type::table($tb) WHERE ...} statement
* — the same trust model as {@link #query(String)}. Never concatenate untrusted
* values into it; use {@link #selectLive(String, String, Map)} to bind values
* safely.
*
* <p>
* Like {@link #selectLive(String)}, this method blocks until the live query
* subscription is fully established on the server, and subscription errors
* (e.g. the table does not exist, or the {@code where} expression is invalid)
* are thrown immediately as a {@link SurrealException} rather than being
* deferred to the first {@link LiveStream#next()} call.
*
* @param table
* table name to watch (must already exist)
* @param where
* raw SurrealQL condition (without the {@code WHERE} keyword), e.g.
* {@code "age > 18"}
* @return a LiveStream delivering only matching notifications; the caller must
* call {@link LiveStream#close()} when done
* @throws SurrealException
* if live queries are not supported, the table does not exist, the
* condition is invalid, or the subscription fails
* @see #selectLive(String, String, Map)
*/
public LiveStream selectLive(String table, String where) {
return selectLive(table, where, java.util.Collections.emptyMap());
}

/**
* Starts a filtered live query on the given table with parameter bindings and
* returns a blocking stream of notifications (CREATE, UPDATE, DELETE) for the
* records that match the given {@code WHERE} condition. This mirrors the JS
* SDK's {@code live(table).where(expr)}.
*
* <p>
* The {@code where} argument is a raw SurrealQL expression appended verbatim to
* the generated {@code LIVE SELECT * FROM type::table($tb) WHERE ...} statement
* — the same trust model as {@link #query(String, Map)}. Values should be
* referenced as {@code $name} placeholders and supplied through {@code params}
* (bound exactly like {@link #query(String, Map)}), which is the safe way to
* inject values:
*
* <pre>{@code
* Map<String, Object> params = new HashMap<>();
* params.put("minAge", 18);
* try (LiveStream stream = surreal.selectLive("person", "age > $minAge", params)) {
* ...
* }
* }</pre>
*
* <p>
* The parameter name {@code tb} is reserved (it binds the table name) and is
* rejected. Like {@link #selectLive(String)}, subscription errors (e.g. the
* table does not exist, or the {@code where} expression is invalid) are thrown
* immediately as a {@link SurrealException} rather than being deferred to the
* first {@link LiveStream#next()} call.
*
* @param table
* table name to watch (must already exist)
* @param where
* raw SurrealQL condition (without the {@code WHERE} keyword),
* referencing {@code params} as {@code $name} placeholders
* @param params
* parameter values to bind to the condition
* @return a LiveStream delivering only matching notifications; the caller must
* call {@link LiveStream#close()} when done
* @throws SurrealException
* if the condition is null or empty, a reserved parameter name is
* used, live queries are not supported, the table does not exist,
* the condition is invalid, or the subscription fails
*/
public LiveStream selectLive(String table, String where, Map<String, ?> params) {
if (where == null || where.trim().isEmpty()) {
throw new SurrealException("The where condition must not be null or empty");
}
final Map<String, ValueMut> valueMuts = ValueBuilder.convertParams(params);
final String[] keys = valueMuts.keySet().toArray(new String[0]);
final long[] values = new long[keys.length];
for (int i = 0; i < keys.length; i++) {
values[i] = valueMuts.get(keys[i]).getPtr();
}
return selectLiveWhere(getPtr(), table, where, keys, values);
}

/**
* Stops the live query with the given id. After the server processes the kill,
* the query delivers no further notifications.
Expand Down
Loading
Loading