Add set() and unset() session variables#191
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
emmanuel-keller
left a comment
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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>
JS-parity gap
The JS SDK exposes
set(variable, value)/unset(variable)on a session (packages/sdk/src/api/session.ts, backed by the RPClet/unsetmethods) to define and remove session variables that are usable as$variablein subsequent queries. The Java SDK had no equivalent: nothing oncom.surrealdb.Surrealand nothing at the JNI layer.Reproduction
On the embedded
memoryengine:surreal.query("LET $x = 42")followed by a separatesurreal.query("RETURN $x")returnsNONE— aLETvariable does not persist acrossquery()calls."LET $x = 42; RETURN $x"), the variable does carry into later statements and returns42.So there was no way to define a variable that outlives a single
query()call.The fix
public Surreal set(String key, Object value)andpublic Surreal unset(String key)oncom.surrealdb.Surreal(returningthisfor chaining, likeuseNs). The value is converted exactly like query bindings (ValueBuilder.convert→ValueMut, with the pointer passed over JNI).Java_com_surrealdb_Surreal_set/Java_com_surrealdb_Surreal_unsetinsrc/main/rust/surreal.rs, calling the Rust SDK'sSurreal::set(key, value)/Surreal::unset(key)(per-session commands in surrealdb 3.1.5).newSession()starts with a snapshot of the variables defined at clone time; afterwards each session's variables evolve independently (set/unseton one session is not visible on the other). Javadoc onset,unset, andnewSessionreflects this and theLET-does-not-persist behavior.Tests
New
SessionVariableTests(memory engine): theLETreproduction,setthenRETURN $keyacross multiple separatequery()calls, overwriting a variable, complex values (map, list,RecordId— includingSELECT * FROM $rid),unsetreturningNONEafterwards,unsetof an undefined variable as a no-op, and thenewSession()snapshot/independence semantics.Full suite:
./gradlew test— 342 tests, 0 failures.🤖 Generated with Claude Code