Skip to content
NEW

Introducing SurrealDB Studio, the new official app of SurrealDB

Learn more

1/5

Release 3.3

3 patch releases · Latest 3.3.0-beta.3 on Aug 20, 2026

The third 3.3 beta moves the permission and COMPUTED write rules to runtime enforcement, and fixes the embedded JavaScript engine's storage lifetime and…

3.3.0-beta.3

PRE-RELEASE

Released on Aug 20, 2026

The third 3.3 beta moves the permission and COMPUTED write rules to runtime enforcement, and fixes the embedded JavaScript engine's storage lifetime and packaging found while wiring the SDK against the published v3.3.0-beta.2 packages. Everything new in 3.3 from v3.3.0-beta.1 and v3.3.0-beta.2 is included and not repeated here.

Items tagged [Enterprise] on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

  • [Query] Permission and COMPUTED write rules move to runtime enforcement. v3.3.0-beta.1 added a definition-time check that tried to decide, when a schema object was defined, whether a PERMISSIONS guard or COMPUTED body could ever reach a data-modifying statement. Seeing through a function call means resolving the callee's stored body against the catalog, and that answer is not sound: whether a body reaches its write depends on which branch its arguments select, so a function that conditionally calls CREATE / UPDATE was refused for every caller, including the guards and COMPUTED fields that never take that branch. That check is dropped: a PERMISSIONS clause loses its definition-time write refusal entirely, and a COMPUTED body loses it for writes reached through a function call. A write written directly in a COMPUTED body is still refused at DEFINE / ALTER time. A DEFINE / ALTER that stores a writing SELECT guard, or a COMPUTED body that reaches a write through a function call, is now accepted, and fails at the point a read actually reaches the write, with the existing A PERMISSIONS clause cannot contain a statement that modifies data / A COMPUTED clause cannot contain a statement that modifies data. The runtime frame is exact regardless of call depth or indirection (eval, scripts, closures arriving as data), which the definition-time check could never be. create / update / delete permission clauses may carry side effects, since they are reached only from a statement that is already writing. This reverses the definition-time behaviour described under Writing computed fields and functions are refused at definition time in v3.3.0-beta.1.

  • [Embedded] Per-platform binary packages for @surrealdb/node-native. The eight native binaries are no longer bundled inside the root package. Each is published as @surrealdb/node-native-<platform> carrying its own os / cpu / libc, and the root package lists them all under optionalDependencies, so an install downloads only the one binary its host can use rather than all eight, keeping a @surrealdb/node install under AWS Lambda's 250 MB unzipped limit.

  • [Embedded] free() on @surrealdb/node-native now releases the datastore's storage before it resolves, so a file-backed datastore can be reopened on the same path after close() within one process. surrealkv releases its lockfile late in shutdown and Drop only spawned a detached close, so free() previously returned with the data directory still locked. The wasm engine has no file backends and is unaffected.

  • [Embedded] The surrealkv+versioned:// scheme, which has named no backend since versioning moved to the ?versioned=true parameter, now reports the supported spelling instead of the generic "unable to load the specified datastore", which read like a build compiled without the backend.

  • [SDK] Query-stream begin frames now carry the protocol revision, and the structural contract a client can rely on when decoding is written down: a stream tag is never repurposed, unknown tags and fields are ignorable, and single is always present on a finished frame (it was already on the wire; only the SDK type made it optional).

  • The mutable_permissions experimental capability is removed. It existed only to reopen the create / update / delete guards that the definition-time rule had shut, and was on for every server already. Remove any --allow-experimental mutable_permissions / --deny-experimental mutable_permissions flag and the SDK's ExperimentalFeature::MutablePermissions builder entry; there is no replacement, since those guards now permit side effects unconditionally.

  • The surrealdb_statement_mutable_permission_writes_total metric is removed along with the capability. Drop any dashboard panel or alert that referenced it.

Upgrade or install

Get SurrealDB v3.3.0-beta.3

Pick how you want to install or upgrade. SurrealDB Studio can update connected instances in place, or choose a platform below to copy a CLI command for v3.3.0-beta.3.

You can upgrade your SurrealDB Cloud instance to v3.3.0-beta.3 effortlessly through SurrealDB Studio.

  1. Select your organisation and instance
  2. On the dashboard, click on the "Upgrade" button
  3. Your instance will be updated and restarted automatically

3.3.0-beta.2

PRE-RELEASE

Released on Aug 18, 2026

The second 3.3 beta is a focused round of startup, shutdown, and readiness hardening for clustered deployments, driven by incidents observed on production multi-node clusters. Everything new in 3.3 from v3.3.0-beta.1 is included and not repeated here.

Items tagged [Enterprise] on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

  • [Server] Configurable readiness heartbeat window. The /ready probe treats a node as unhealthy once its cluster heartbeat goes stale, and that window can now be set directly with --readiness-heartbeat-max-age / SURREAL_READINESS_HEARTBEAT_MAX_AGE. Previously it was always derived as three times the node-membership refresh interval (9s by default), which welds the probe to the very write path it measures. That is fine for a storage engine whose node-row write is local and sub-millisecond, but under a distributed engine the same write is a consensus transaction whose latency moves with cluster health, so a slow-but-healthy write path made every replica flip NotReady at once and presented partial degradation as total unavailability. The default is unchanged, so behaviour is identical unless you set the flag. Startup now also warns if the configured window reaches the interval at which peers archive an unresponsive node (30s), since a node reported ready after its peers have written it off keeps taking traffic while its cluster registration and live queries are collected underneath it.

  • [Server] Graceful shutdown of startup background work. Deferred startup tasks - a startup import, root credential creation, the Surrealism eager module load, and the first node-maintenance pass - now register with the datastore, so shutdown waits for them (bounded by the existing 30-second maintenance shutdown timeout) instead of closing the storage engine while they are still writing. Cancellation is prompt: each task selects on the shutdown token, an aborted import deliberately leaves the node not ready rather than flipping a dying pod healthy, and unreached Surrealism modules simply load on first use.

  • [Server] Archived-node cleanup no longer gates the HTTP listener. On a cluster recovering from a restart storm, the listener could take minutes to become reachable after the storage engine was ready: the bind waited behind sequential startup operations, and remove_nodes opened one write transaction per archived node with no bound on how much dead-member residue a restart storm left behind. The first expiry and cleanup pass is now spawned after this node registers itself, not awaited, so it cannot hold up the bind. The periodic maintenance scheduler is unchanged.

  • [Server] The cluster heartbeat now survives a slow write. It previously made a single attempt per tick under a fixed 60s budget, so one slow write could lose readiness roughly 51s before the attempt itself failed. Each tick now spends its whole budget - bounded relative to the readiness window - on the write and retries a fast failure within the tick, so a write that is slow but working still lands in time to keep the node ready.

  • [Enterprise] The read-catch-up completeness watermark is now persisted, so a restart no longer re-drains the committed log from zero. The floor was held in memory only, so every process start walked the entire committed history - one quorum round trip per 100 records - under a begin whose fixed 300s request timeout it eventually exceeded; the startup gate treated that as fatal and exited, and the next start walked from the same zero floor, crashlooping. The watermark now lives in the durable meta column family, is restored on open clamped to the durable committed frontier, and is established at startup under a begin bounded on progress rather than wall time. Measured on a 3-node cluster: a rollout that had been crashlooping against a 30-minute timeout completed in 5m04s, the startup gate went from 300s-then-exit to single-digit milliseconds, and watermark-caused readiness 503s went to zero.

  • [Enterprise] A pending admission freeze no longer wedges startup. A guard-holding wait inside the read catch-up - the committed-manifest window pull, with 60s of patience per page in an unbounded page loop - could hold its guard across a Normal-leaving view transition, so the freeze never drained and the startup warm-up failed. Because that path exits the process on any error, the node killed itself and restarted into the same state. Those waits are now raced against the admission-freeze marker and bail retriably, and the warm-up absorbs the transients under a single request budget instead of surfacing them. Store replays and offloaded reads are deliberately not preempted, since dropping an offloaded closure would strand a durable write ahead of its post-apply bookkeeping.

  • [Enterprise] A begin or read establishment refused because a view transition is pending is now reported as a retryable TransactionConflict rather than a hard datastore error. The commit path deliberately keeps its unknown-outcome classification, since a commit interrupted mid-round cannot know whether other replicas still hold tentative reservations.

  • [Enterprise] If you applied SURREAL_DS_STARTUP_NORMAL_TIMEOUT=0 as a break-glass on v3.3.0-beta.1, unset it on this build. The startup warm-up now retries through view-transition churn, so =0 only removes protection - it disables both enterprise startup gates and leaves check_version as the only one.

  • [Enterprise] Keep SURREAL_STARTUP_OPERATION_TIMEOUT=180 for now. The default is still 60s, which is tight for a consensus engine on a cold cluster.

Newer patch available

Upgrade to 3.3.0-beta.3

You are viewing the 3.3.0-beta.2 changelog. A newer patch in this release line is available - we recommend running 3.3.0-beta.3 for the latest fixes and improvements.

View 3.3.0-beta.3 release notes

3.3.0-beta.1

PRE-RELEASE

Released on Aug 14, 2026

This is the first beta in the 3.3 series. It opens the line with two new ways to connect - a Postgres wire-protocol listener that lets any Postgres client run SurrealQL or ISO GQL, and a gRPC engine with end-to-end result streaming - and makes ISO GQL available by default. File buckets gain S3, GCS, and Azure object-storage backends, the Node.js and browser embedded engines are rebuilt against the current engine, and a deep round of index work lands: bitmap index fusion, pre-filtered vector search, and a much faster full-text write path. For SurrealDS (Enterprise), cold-start convergence is dramatically faster, recovery cost is bounded, and cluster storage can now be S3-backed.

The items below cover what is new in the 3.3 line. The fixes already released across the v3.2.1v3.2.4 patches are also included in this build but are not repeated here.

Items tagged (Enterprise) on a heading or [Enterprise] on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.

SurrealDB can now speak the Postgres wire protocol (v3.0), so any Postgres client - psql, JDBC, npgsql, tokio-postgres, and the rest - can connect and run SurrealQL or ISO GQL with properly typed results.

  • Opt-in via --postgres-bind <addr> / SURREAL_POSTGRES_BIND, and capability-gated through a new postgres route target. TLS is available through the standard SSLRequest upgrade, reusing --web-crt / --web-key.

  • Both the simple and extended query protocols are supported (Parse/Bind/Describe/Execute/Close/Sync/Flush), with text and binary codecs for every mapped type including numeric. The namespace and database come from the startup database parameter as ns/db, with USE and LET persisting across queries; interactive BEGIN/COMMIT/ROLLBACK behaves like Postgres, including 25P02 aborted-transaction poisoning, and CancelRequest is supported.

  • The query dialect is selectable at connect time (options=-c dialect=gql) or in-session (SET dialect), so a Postgres client can run ISO GQL directly.

  • Authentication supports cleartext passwords over TLS and SCRAM-SHA-256 (RFC 5802 / RFC 7677). DEFINE USER ... PASSWORD now derives SCRAM verifier material alongside the existing Argon2 hash, and a new additive PASSSCRAM '<verifier>' clause on DEFINE USER / ALTER USER imports a precomputed PostgreSQL-format verifier so exports round-trip losslessly. Existing users without SCRAM material keep working.

  • Postgres positional parameters $1..$n are rewritten to $_1..$_n, and SurrealDB auto-commits each top-level statement - use an explicit BEGIN ... COMMIT for all-or-nothing behaviour. ANSI-SQL translation, pg_catalog emulation, MD5/COPY, and LIVE queries are out of scope for now.

ISO GQL querying - introduced experimentally in 3.2 - is now available by default, matching GraphQL. The --allow-experimental gql flag, the SDK experimental-feature builder, and the gql cargo feature all remain valid as harmless no-ops, so existing configurations keep working. GQL is reachable over the /gql HTTP route, the gql RPC method, MCP, and the new Postgres listener.

The SDK's engines now sit behind one typed SurrealEngine interface, and a new native gRPC engine joins the family alongside major streaming work across every transport.

  • New grpc:// / grpcs:// connection schemes, shipped enabled in the released surreal binary, so surreal sql --endpoint grpc://host:port works out of the box.

  • Query results stream end to end over gRPC via db.query(..).stream_items() - the client no longer waits for the whole result set to be produced. Measured on a 10,000-row SELECT, the first rows reach the client 65 µs after execution begins, against ~20 ms to produce them all. Rows are provisional until their statement's terminal frame arrives, which is withheld until the outcome is final.

  • The WebSocket RPC protocol gains a query_stream request answered by a sequence of frames (begin, per-statement rows/value/finished, then one end), carried in JSON, CBOR, and flatbuffers alike. This brings streaming to the places gRPC cannot reach: browsers/WASM and Cloudflare Workers.

  • The embedded JavaScript engines stream now too: a QueryStream with next() in @surrealdb/node-native and a ReadableStream in @surrealdb/wasm-native, with bounded buffering so nothing runs ahead of the reader, and abandoned streams stopping their execution instead of orphaning a transaction.

File buckets (DEFINE BUCKET and the file::* functions) gain cloud object-storage backends in the open-source engine, built on the object_store crate.

  • AWS S3 and S3-compatible stores (MinIO, Backblaze B2, Wasabi, Cloudflare R2) via s3://, s3+http://, s3+https://; Google Cloud Storage via gs:// / gcs://; Azure Blob Storage via az:// / azure://. The existing S3 URL format is preserved exactly, and a latent bug that dropped a custom endpoint's port is fixed.

  • Bucket traffic is now metered: three authenticated-only OpenTelemetry instruments under surrealdb.bucket - sent_bytes, received_bytes, and operations.

  • Embedders can install a custom object store behind buckets with Builder::with_bucket_store_provider(...) - the seam needed to back buckets with a platform store such as a Cloudflare Workers R2 binding.

The streaming planner can now compose index results as bitmaps over a shared per-table document-ID space, and vector search can use those bitmaps to filter before traversal.

  • Index-backed predicates compose with AND/OR/NOT as compressed bitmaps, and a COUNT answerable from indexes alone never touches records. New plan nodes (BitmapIndexScan, BitmapFullTextScan, BitmapAnd, BitmapOr, BitmapAndNot) are visible in EXPLAIN, with per-node candidate cardinalities in EXPLAIN ANALYZE. Existing b-tree indexes keep working with no rebuild. Non-anchor range branches are capped by SURREAL_BITMAP_BRANCH_BUDGET (default 250,000 entries; 0 disables), and the full WHERE clause is always retained as a residual filter.

  • Pre-filtered vector search. When a KNN plan is chosen, WHERE conjuncts that provably match an index bitmap are evaluated into an allow-list before HNSW/DiskANN traversal, replacing one record fetch per visited graph node. An adaptive triage picks a tier, reported in EXPLAIN ANALYZE as prefilter_tier: exact (small allow-lists skip the graph entirely for guaranteed-correct top-K, threshold SURREAL_KNN_PREFILTER_EXACT_THRESHOLD, default 2,000), graph (gated traversal with a boosted search width, up to SURREAL_KNN_PREFILTER_EF_BOOST_THRESHOLD, default 100,000), and graph_unboosted above that, with a fallback tier reported when the allow-list cannot be built within the bitmap branch budget. Enabled by default; SURREAL_KNN_PREFILTER_ENABLED=false turns it off.

The write path of the concurrent full-text index has been overhauled end to end.

  • Full-text delta-log entries are batched per transaction instead of one key per (term, document), and postings are now keyed by document rather than by term which significantly reduces write amplification. Older indexes are read through a legacy fallback, so no migration is needed.

  • COUNT-index deltas are aggregated per transaction, and the compactor is woken by the commit that queues its work rather than only by its timer.

  • Exports now size each emitted INSERT statement by the KV keys a record will write given the table's index set, so restores of heavily indexed tables use appropriately sized transactions.

A major round of work on SurrealDS cluster formation, recovery cost, and overload behaviour.

  • Faster cold-start convergence. Replicas leaving recovery at different times could stall cluster formation on retry timers. View-change retransmission, a formation-aware backoff, and concurrent outcome-donor drains cut measured convergence tails from 4.44 s to 0.02 s (n=3) and 14.75 s to 4.61 s (n=5), and the default-config worst case from 86.9 s to 13.7 s.

  • Bounded recovery drains. The Phase-1 outcome drain previously re-paged a donor's entire outcome history on every entry into recovery - O(all transactions ever committed). Each replica now keeps a bounded in-memory write-order journal and a recovering replica asks each donor only for what it wrote since its last position, falling back to the full stream when needed. Configured via SURREAL_DS_RECOVERY_OUTCOME_JOURNAL_ENTRIES (default 262,144; 0 disables). Drain behaviour is observable on new surrealdb.ds.recovery_outcome_drain* counters.

  • Bounded transaction write sets. A new operation-count bound on transaction write sets, SURREAL_DS_TRANSACTION_WRITE_SET_LIMIT_OPS (default 100,000; 0 disables), sits beside the existing 32 MiB byte cap: an over-bound transaction fails fast at the coordinator with TransactionTooManyOperations before any network traffic, with a warning fired once at half the bound. Keep the value uniform across the fleet.

  • Membership-epoch correctness. A membership-changing leader now withholds serving until a quorum of the new voter set is confirmed to hold the decided configuration; a coordinator whose epoch was superseded mid-transaction abandons the round promptly instead of resending into a silent fence; and a voter fenced for being ahead of its own cluster now escalates to a view change instead of starving indefinitely. The fence itself is now observable via the surrealdb.ds.epoch_fence_drops counter with a direction label.

  • Mutable permission clauses, behind a capability. The 3.1-era security block on writes in all permission clauses over-reached for create/update/delete clauses, where audit-logging side effects are a relied-upon pattern. Those are re-permitted behind a new transitional mutable_permissions experimental capability - allowed by default on the server (deny it with --deny-experimental mutable_permissions), off by default for embedders. SELECT permission clauses stay read-only. Usage is measurable via the new surrealdb_statement_mutable_permission_writes_total counter. Alongside this, function mutability is now resolved against the stored call graph, so a write hidden behind a user-defined function call can no longer slip past definition-time checks on COMPUTED fields and permission clauses.

  • Datastore versioning and startup migrations. The datastore now records its semantic version and runs registered, ledger-tracked data migrations on startup, resuming interrupted runs, with a per-node version history. The first migration fixes a keyspace collision present since 3.0.0, where DEFINE SEQUENCE keys sorted inside the table-name band and any table whose name began with sq could break INFO FOR DB, sequence exports, and REMOVE NAMESPACE / REMOVE DATABASE. Note for rolling upgrades: a sequence created after the rollout begins is not visible to nodes still on the previous release until they upgrade.

  • Wall-clock query timeouts on the transports. The HTTP (/sql, /gql, /graphql) and RPC (WebSocket and HTTP) surfaces now apply a hard wall-clock timeout reusing the configured --query-timeout value, on top of the existing cooperative deadline. Timed-out HTTP requests return 504 Gateway Timeout with a proper error envelope. Off unless --query-timeout is set; transaction-control methods are exempt, while signin/signup/authenticate (which can run user-defined SurrealQL) are guarded.

  • Runtime-swappable capabilities for embedders. Datastore::set_capabilities(...) atomically swaps the capability set with no datastore rebuild, aimed at embedders that cannot restart per instance (such as a wasm Cloudflare Durable Object serving many tenants). In-flight queries keep the snapshot they started with, and the outbound HTTP client is rebuilt in the same swap so a runtime network tightening is enforced on redirect hops and DNS resolution too.

  • Stateless MCP. The first-party MCP server now serves the stateless 2026-07-28 protocol revision alongside the handshake-based revisions on the same /mcp endpoint. Every tool except use gains optional namespace and database arguments, with scope resolving from call arguments, then surreal-ns / surreal-db request headers, then session state, then server defaults. The advertised protocol revision is now pinned explicitly (upgrading the underlying SDK can no longer silently change it), and the gql tool's annotations no longer declare it read-only - those hints drive client-side auto-approval, and the GQL dialect can write.

  • vector::sum. A new function, in both forms: as a grouped aggregate it folds a vector-valued expression across a group's rows in O(dimension) state regardless of row count, and as a scalar it sums an array<array<number>> directly - making an in-database weighted centroid composable from existing functions.

  • New vector functions, stricter numeric edge cases. vector::distance::mahalanobis(a, b, cov) and vector::similarity::spearman(a, b) are implemented. A family of silent-NaN or sentinel results now error instead: vector::divide with a zero divisor element, normalize/angle/project at zero magnitude, and pearson/spearman on degenerate inputs; vector::similarity::jaccard now uses true set semantics, and time::nano errors outside the i64-nanosecond range instead of returning 0.

  • ORDER BY outside the projection. SELECT event, subject FROM audit_log ORDER BY at now works - the sort field no longer has to appear in the projection.

  • Brute-force KNN with computed query vectors. The streaming executor now supports <|k, DIST|> with a non-literal query vector (bind parameters, function calls, computed arrays), closing the last unimplemented case in the read-only planner.

  • Multi-part MATCHES. A full-text @@ reached through a record link to an index on another table (t.name @@ 'x') now executes on the streaming engine.

  • Index matching through record traversals. A WHERE clause matching a compound index through record-idiom traversals of a parameter (for example WHERE createdAt <= $scan.task.finishedAt) previously always full-table-scanned; row-independent traversals are now resolved at plan time so index analysis can match them, with strict provable-equivalence conditions.

  • Silo package resolution. Surrealism silo packages now resolve over HTTPS from a configurable endpoint (default https://silo.surrealdb.com), with a 256 MiB cap and organisation- and package-name restrictions that block path traversal. The fetch needs no allow_net grant since the host comes from server configuration.

  • Import and scan performance. Collection literals whose elements are already values now convert without the async evaluator (a 200K-record import profile had 52.9% of CPU under literal evaluation); mock targets (|table:N|) are drained id-by-id instead of pre-expanding a million-entry list; nine scan loops stopped copying whole cursor batches; and a delete's reference batch is decoded once instead of per entry.

  • Quieter, correct system metrics. One system-metrics refresher now runs per process regardless of datastore count (embedders with several datastores got CPU figures from arbitrary sub-intervals), and the metrics cache is populated before the first query so INFO FOR ROOT can no longer report an all-zero system block.

  • Storage engine updates. The memory backend adopts surrealmx 0.24's native savepoint release, removing an emulated savepoint stack; affinitypool 0.8 speeds up the blocking-work pools and fixes a pool deadlock; TiKV gRPC status failures are now logged with their code and message at debug level instead of being collapsed.

  • [Enterprise] Recovery and starvation observability. The Phase-1 outcome drain's certification behaviour is surfaced on three new counters; the starved-begin metric is split by starvation class (below_quorum, no_local_reply - dashboards summing only anterior_prepared will now under-report); and the log volume of a membership reconfiguration is bounded and rate-paced.

  • [Server] USE NS <expr> / USE DB <expr> with a subquery argument hit an internal panic; such statements now return an ordinary query error with the transaction rolled back.

  • [Query] A statement's read-only classification had three holes (SELECT clauses like LIMIT (CREATE ...), RETURN ... FETCH, and several idiom parts), so writing statements could run on read transactions and fail partway through.

  • [Query] rand::* calls in a WHERE clause were folded into a single plan-time constant on the streaming engine, making the predicate all-or-nothing across rows; they now evaluate per row, as SQL engines do for volatile functions.

  • [Query] Numeric hash/equality mismatches on the streaming engine: GROUP BY could split one value into two groups, and IN / INSIDE / CONTAINSANY / CONTAINSALL could silently drop rows when a column mixed float and decimal representations of the same number. Grouping and set-membership now key on ordering, matching =.

  • [Query] CONTAINSALL with mixed-representation literals dropped rows on the index-overlap path; literal equivalence classes now each get exactly one bitmap bit. Separately, a UnionIndexScan could silently drop a CONTAINSANY / ANYINSIDE conjunct from its residual filter and return rows that did not match the original WHERE clause.

  • [Query] RETURN DISTINCT emitted one value as two rows when float and decimal forms of the same number hashed differently; dedup and hash-joins now key on ordering, which also halves peak join build memory.

  • [Query] [WHERE ...] applied to a non-collection returned a one-element array instead of NONE, and sets were not filtered element-wise. Separately, ORDER BY ties are now stable (input order as the final key), so a LIMIT cutting inside a tie group can no longer repeat or drop rows across pages.

  • [Query] The streaming executor's MATCHES fallback returned true unconditionally (WHERE 'abc' @@ 'zzz' matched every row); and the legacy engine's cross-table MATCHES evaluated a tautology that matched every row. Roughly 490 unit tests were backfilled across the executor at the same time.

  • [Query] BREAK / CONTINUE raised inside a LET binding's value were swallowed by the streaming executor instead of propagating to the enclosing loop.

  • [Query] PERMISSIONS predicates reaching the row via $parent denied every row on the streaming engine, and math::variance / math::stddev returned population figures where sample figures were correct, in both ad-hoc GROUP BY and materialized views. The field-permission pass could also cut whole array elements out of results, causing silent data loss including an empty RETURN DIFF for non-owner sessions.

  • [Query] A closure body's writes are now reflected in its access mode, so array::map with a writing closure can no longer be scheduled on read-only execution paths; likewise an unresolvable mod:: function's write flag now defaults to writeable.

  • [Query] 85 signature divergences between the streaming function registry and the legacy layer were aligned; six api::* middleware functions were missing from the streaming registry entirely, and several async builtins silently ignored extra arguments instead of erroring. object::matches - advertised by the parser but implemented in neither registry, so every call failed at runtime - is now rejected cleanly at parse time.

  • [Query] Values now coerce into set types, so SET field += value on a set<...> field works, and deduplication is applied after a field's VALUE clause runs rather than before.

  • [Index] Any index rebuild could publish a durably Online but empty index (the compactor's generation guard was wiped with the build subspace), after which every query against it silently returned nothing. Covers DEFINE INDEX, DEFINE INDEX OVERWRITE, and REBUILD INDEX.

  • [Index] Rolling back a DEFINE INDEX ... CONCURRENTLY could leave durable build state behind, because cleanup deleted the build keys while the builder task was still writing.

  • [Index] A full-text term's compacted document set could depend on how compaction rounds grouped its deltas, so a document could keep a term it had lost (a false MATCHES hit) with nothing to correct it later; compaction now carries a per-term residual. Full-text compaction rounds are also now bounded by a document budget rather than a key count.

  • [Index] DiskANN KNN memory and latency grew without bound under sustained write load (OOM-cycling on capped pods); the pending-update set is now sharded so per-query work tracks the active backlog.

  • [RPC] A single client could permanently deadlock its own session across 15 RPC methods over WebSocket, HTTP, or gRPC, when a buffered query overlapped a session-mutating call (set, signin, use, ...).

  • [RPC] Three paths dropped writeable transactions without committing or cancelling them - worst was async event processing, where an undecodable queue entry leaked a transaction on every batch indefinitely. Present since 3.1.

  • [RPC] Live-query registry leaks: an ended live query kept its registry entry and its surrealdb.live_query.active gauge increment for the life of the connection; REMOVE TABLE/DATABASE/NAMESPACE and principal revocation are now covered, and gRPC streams delete subscriptions the client never claimed.

  • [RPC] Dropping a client-side item stream mid-query leaked its transaction and driving tasks for the life of the process; the execution now stops when the stream is dropped. The per-connection WebSocket stream cap is also now claimed atomically.

  • [SDK] Cloning a Surreal handle on the WebSocket engine replayed session state fire-and-forget, so the clone's first request could execute before its own signin - reproduced at roughly 1% of iterations. Requests now park until the replayed setup is acknowledged.

  • [Embedded] db.use_defaults() cleared the session instead of applying DEFINE CONFIG DEFAULT, so embedded consumers silently ignored configured default namespaces/databases.

  • [Embedded] A query abandoned mid-batch left a half-written transaction in the session that a later commit would commit; abandoned queries now take their transaction with them. The session registry no longer auto-creates entries for already-ended sessions.

  • [Export] A database with an ENFORCED relation table could not restore its own export - every edge was silently dropped while the restore reported success. The endpoint check is now deferred under OPTION IMPORT, fixing existing export files too.

  • [HTTP] /signin and /signup each applied the other's body-size limit; each route now uses its own. On wasm, http::* calls failed capability checks outright and carried no User-Agent; both fixed. fetch() with redirect: "error" / "manual" could not fetch any URL because its client was built from the wrong rule set.

  • [KV] Releasing a savepoint discarded the wrong scope's undo entries on every backend, so a record created by a statement that later failed could survive along with its index writes (reachable via synchronous DEFINE EVENT). Also fixed a TiKV regression that roughly doubled write-path RPCs for multi-record INSERT/UPSERT after any savepoint release.

  • [KV] surrealkv is updated to 0.21.3, carrying two durability fixes - a missing fsync after compaction that could leave a SIGKILL'd pod unable to start, and a memtable rotation bug.

  • [KV] The DEFINE API handler's transaction now applies the statement write-cardinality guard (SURREAL_TRANSACTION_MAX_WRITE_KEYS), which previously did not cover custom API handlers.

  • [Parser] More legacy SurrealQL accepted by the new parser (SET idiom forms, function-call sleep, keyword record ids in RELATE targets), and RecordId::parse_simple no longer adds a layer of backticks on every round trip.

  • [Enterprise] Recovery could abort a committed, durably applied transaction when a replica's last transaction-id-keyed evidence was reaped exactly as its applied gate was set; votes are now retained until a real id-keyed decision exists.

  • [Enterprise] OCC read sets are now validated against the writes that can actually invalidate them, tightening isolation correctness.

  • [Enterprise] An indeterminate commit is now reported as CommitOutcomeUnknown - telling the client to read back rather than replay - instead of a definite "nothing was written" error.

  • [Enterprise] Both Transactable implementations discarded a released savepoint's undo state, mirroring the community savepoint fix.

The gql experimental capability gate is removed and ISO GQL querying is available by default. Deployments that relied on it being off should control access via the gql route target and the standard capability configuration. The old --allow-experimental gql spelling remains accepted as a no-op.

UPDATE and UPSERT now evaluate the WHERE condition before the data clause, so a side-effecting data clause (for example SET spawn = (CREATE log).id) no longer runs for records the condition rejects - previously the side-effect count could depend on whether the planner picked an index. The condition and data clause now share a single pre-mutation snapshot, so reads never observe the statement's own writes, and field clauses evaluate in dependency order rather than alphabetically. Review statements that relied on assignment-visibility or alphabetical field ordering.

Three syntax changes to the experimental Surrealism module surface: the executable form now uses FROM instead of AS (DEFINE MODULE mod::color FROM f"modules:/color.surli" UNSIGNED;), a trailing UNSIGNED keyword is now required on both executable forms, and the silo version segment is now written silo::{org}::{pkg}::<1.0.0> (with :: before the version). Stored definitions are unaffected and re-render in the new syntax.

The memory backend's versioned-read support is removed: VERSION clauses return an unsupported-versioned-queries error, matching indxdb, and the backend now rejects versioned / retention connection parameters at startup instead of accepting them and failing later. Version coverage lives in the rocksdb and surrealkv backends, which retain native versioning.

All seven duration::set_* functions were parser-advertised but never implemented - every call failed at runtime. They now produce a clean parse-time error.

A COMPUTED field or permission clause whose body writes through a user-defined function call was previously accepted (and wrote on every read); function mutability is now resolved against the stored call graph and such definitions are refused at DEFINE/ALTER time.

The SDK's engine layer is now the typed SurrealEngine trait (one method per operation) rather than a command enum over a channel; SDK futures remain Send but are no longer Sync. surrealdb-core no longer re-exports surrealdb-rpc, so Rust consumers must depend on it directly. Datastore::transaction(..) loses its ignored lock-type argument, and Datastore::get_capabilities() returns an owned Arc<Capabilities>.

@surrealdb/node is split: the native addon is published as @surrealdb/node-native and the SDK engine implementation moves to surrealdb.js. The strict connection option - advertised in the typings but silently ignored - is removed and is now a type error. The wasm module is renamed surrealdb_wasm and published as @surrealdb/wasm-native.

Newer patch available

Upgrade to 3.3.0-beta.3

You are viewing the 3.3.0-beta.1 changelog. A newer patch in this release line is available - we recommend running 3.3.0-beta.3 for the latest fixes and improvements.

View 3.3.0-beta.3 release notes

Our newsletter

Get tutorials, AI agent recipes, webinars, and early product updates in your inbox every two weeks

GET STARTED

Start building with SurrealDB

The unified data layer for AI. Simplify your stack. Reduce complexity. Build faster.

SamsungNVIDIAAppleVerizonTencent

SOC 2 Type 2

GDPR

Cyber Essentials Plus

ISO 27001

SurrealDB

The unified data layer for AI

Graph, vector, document, and relational in one engine.
Agent Memory that connects and retrieves context wherever your data lives.

Explore with AI

Stay in the loop

Tutorials, AI agent recipes, and product updates, every two weeks.

Independently verified

SOC 2 Type 2

GDPR

Cyber Essentials Plus

ISO 27001

Trust Centre

Copyright © 2026 SurrealDB Ltd. Registered in England and Wales. Company no. 13615201

Registered address: 3rd Floor 1 Ashley Road, Altrincham, Cheshire, WA14 2DT, United Kingdom

Trading address: Huckletree Oxford Circus, 213 Oxford Street, London, W1D 2LG, United Kingdom