Skip to content

Add set() and unset() session variables#191

Open
welpie21 wants to merge 3 commits into
surrealdb:mainfrom
welpie21:feature/session-variables
Open

Add set() and unset() session variables#191
welpie21 wants to merge 3 commits into
surrealdb:mainfrom
welpie21:feature/session-variables

Conversation

@welpie21

Copy link
Copy Markdown

JS-parity gap

The JS SDK exposes set(variable, value) / unset(variable) on a session (packages/sdk/src/api/session.ts, backed by the RPC let/unset methods) to define and remove session variables that are usable as $variable in subsequent queries. The Java SDK had no equivalent: nothing on com.surrealdb.Surreal and nothing at the JNI layer.

Reproduction

On the embedded memory engine:

  • surreal.query("LET $x = 42") followed by a separate surreal.query("RETURN $x") returns NONE — a LET variable does not persist across query() calls.
  • Within a single multi-statement call ("LET $x = 42; RETURN $x"), the variable does carry into later statements and returns 42.

So there was no way to define a variable that outlives a single query() call.

The fix

  • public Surreal set(String key, Object value) and public Surreal unset(String key) on com.surrealdb.Surreal (returning this for chaining, like useNs). The value is converted exactly like query bindings (ValueBuilder.convertValueMut, with the pointer passed over JNI).
  • Matching JNI exports Java_com_surrealdb_Surreal_set / Java_com_surrealdb_Surreal_unset in src/main/rust/surreal.rs, calling the Rust SDK's Surreal::set(key, value) / Surreal::unset(key) (per-session commands in surrealdb 3.1.5).
  • Verified session semantics and documented them: newSession() starts with a snapshot of the variables defined at clone time; afterwards each session's variables evolve independently (set/unset on one session is not visible on the other). Javadoc on set, unset, and newSession reflects this and the LET-does-not-persist behavior.

Tests

New SessionVariableTests (memory engine): the LET reproduction, set then RETURN $key across multiple separate query() calls, overwriting a variable, complex values (map, list, RecordId — including SELECT * FROM $rid), unset returning NONE afterwards, unset of an undefined variable as a no-op, and the newSession() snapshot/independence semantics.

Full suite: ./gradlew test — 342 tests, 0 failures.

🤖 Generated with Claude Code

welpie21 and others added 2 commits July 10, 2026 14:49
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@welpie21 welpie21 marked this pull request as ready for review July 10, 2026 14:27

@emmanuel-keller emmanuel-keller left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — approve with nits.

Solid — correctly uses get_surreal_ref! (not the cloning variant), which is why the variable actually lands on the session and persists across query() calls. Value conversion goes through the same ValueBuilder.convert path as query bindings and is cloned native-side, matching every sibling. No new leak/double-free. Javadoc (incl. the LET-vs-set distinction and the newSession() snapshot semantics) is thorough.

Test gaps (non-blocking): set(key, null) — worth pinning down since Java null → SurrealDB NULL vs NONE is a real distinction; a reserved/invalid name exercising the @throws SurrealException path; and set interacting with a same-named per-query binding.

* @throws SurrealException
* if the variable cannot be set
*/
public Surreal set(String key, java.lang.Object value) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a one-line comment here noting that the value is cloned native-side and the ValueMut wrapper is reclaimed by its finalizer — the ownership isn't obvious at the call site (it does correctly match create/run/signup and the query-binding path).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified and added in 05d36a1. On the Rust side, Java_com_surrealdb_Surreal_set resolves the pointer via get_value_mut_instance! -> get_instance, which only borrows (raw-pointer deref, no Box::from_raw), and then passes value.clone() to the SDK's set — so the value is indeed cloned native-side and the Java ValueMut wrapper retains ownership of the original allocation, reclaimed by Native.finalize() (moved() is never called on this path). This matches signup/signinRecord/run and the create/query-binding paths, which all borrow + .clone() the same way. Added a short comment at the call site stating this.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants