3.1.4
Released on Jun 10, 2026
SurrealDB 3.1.4 is a patch release on the 3.1 line. It addresses a permission-enforcement vulnerability in array element-level SELECT permissions, alongside a query-planner correctness fix, a permission fix for the experimental file buckets feature, and a build fix for asynchronous Surrealism modules.
The security fix addresses array element-level SELECT permissions that could leak denied elements. We recommend that all users on the 3.1 line upgrade.
v3.1.4 is a drop-in upgrade from any earlier 3.1.x release, with no SurrealQL surface changes and no catalog or on-disk layout changes.
v3.1.4 is a patch release with 1 security fix and 3 bug fixes across the 3.1 series.
Security
Array element-level SELECT permissions leak denied elements [Moderate] - GHSA-8rw6-p7m8-63jp
A field permission on an array's elements (for example PERMISSIONS FOR select WHERE ... on items[*]) expanded the array in ascending index order and removed each denied element with an in-place Vec::remove, which shifts every later element down. Cutting forward therefore invalidated the pending indices: a deny-all permission leaked the odd-indexed elements, and a per-element predicate kept or dropped the wrong elements. All affected paths now cut elements in reverse index order, so denied elements are removed by identity and permitted siblings are preserved.
Bug fixes
Query engine
Fixed
type::field('id')equality not using the record-id point lookup. A query such asSELECT * FROM item WHERE type::field('id') = item:1(and the reversed-operand form) is now planned as aRecordIdScanpoint lookup, like a bareid, instead of falling back to a full table scan when no user-defined index onidexists. Residual conjuncts such asAND score > 15are still applied after the lookup.
File buckets (experimental)
Fixed bucket
PERMISSIONS WHEREpredicates not being enforced in the streaming file executor. The streaming executor previously could not evaluate a bucket'sPermission::Specificexpression and fell back to a role-only check, allowing record and guest users to bypassDEFINE BUCKET ... PERMISSIONS WHEREclauses onfile::get,file::put,file::copyand related functions. The executor now evaluates the bucket predicate with the$action,$fileand$targetparameters bound, matching the legacy compute path, andlistoperations are checked against theListoperation rather thanExists.
Modules
Fixed building asynchronous Surrealism modules with
surreal module build. The Surrealism SDK relies on Tokio'snetfeature on thewasm32-wasip2target, which currently requires thetokio_unstablecfg flag. The build command now generates a.cargo/config.tomlwith the required build flags (leaving any existing file untouched) and setsRUSTFLAGSaccordingly. Failedcargo buildandcargo metadatainvocations now surface the underlying compiler output instead of a generic error message.
Upgrade or install
Get SurrealDB v3.1.4
Pick how you want to install or upgrade. Surrealist can update connected instances in place, or choose a platform below to copy a CLI command for v3.1.4.
You can upgrade your SurrealDB Cloud instance to v3.1.4 effortlessly through the Surrealist app.
- Open the Surrealist app
- Select your organisation and instance
- On the dashboard, click on the "Upgrade" button
- Your instance will be updated and restarted automatically
3.1.3
Released on Jun 3, 2026
SurrealDB 3.1.3 is a patch release on the 3.1 line. It focuses on connection and SDK robustness, query-engine correctness, and build fixes across more platforms.
Notable fixes include a cold-start "Session not found" race in the engine router, a graph traversal carrying an inline filter that returned no rows, permission predicates that run their own subqueries, and embedded scripting fetch() Blob types. It also restores 32-bit builds and makes indexed k-nearest-neighbour query plans clearer in EXPLAIN.
v3.1.3 is a drop-in upgrade from any earlier 3.1.x release, with no SurrealQL surface changes and no catalog or on-disk layout changes. The one platform note is that DiskANN vector indexes now require a 64-bit target.
v3.1.3 is a patch release with 3 minor improvements and 10 bug fixes across the 3.1 series.
Improvements
Vector search
Clearer kNN query plans.
EXPLAINnow reports the filter pushed into an indexed k-nearest-neighbour search as apredicateattribute on theKnnScanoperator, mirroring howTableScanreports its own predicate. Previously an indexed kNN query with a pushed-down filter was indistinguishable in the plan from one without.
Dependencies and storage engine
Embedded
surrealmxstorage engine upgraded to 0.22.0. The upgrade is API-compatible with no query-surface change, and drops a transitive dependency (ferntree) as a precaution against a theoretical race-condition risk.Looser
wasm-bindgenconstraint.wasm-bindgen-futuresis no longer pinned to an exact version, so projects that depend on the SDK alongside a newerwasm-bindgenresolve cleanly.
Bug fixes
SDK and connections
Fixed a cold-start race in the engine router that could surface a spurious "Session not found" error on the first request after connecting.
Idempotent
USEentries are now coalesced in the remote replay log, so reconnecting no longer replays redundant namespace and database selections.In-flight queries are now cancelled when their WebSocket connection closes, instead of continuing to run after the client has disconnected.
Query engine
Fixed a graph traversal carrying an inline filter returning no results. A query such as
SELECT * FROM person:1->(knows WHERE since > 2020)now returns the matching edges instead of an empty array.
Permissions
Permission predicates now evaluate as the system. A permission
WHEREclause that runs its own subquery or graph traversal no longer recursively re-triggers row-level permission checks. Denial behaviour is unchanged.
Scripting
In embedded scripting, the
typeof afetch()responseBlobis now derived from theContent-Typeheader, matching the Fetch specification.
GraphQL
The GraphQL schema cache now evicts entries deterministically, in first-in, first-out order.
Build and platform
Restored 32-bit builds.
i686,armv7, and other 32-bit targets compile again, with DiskANN now gated to 64-bit targets.Fixed a build failure when compiling with only the
protocol-httpfeature, without WebSocket support.Fixed a panic when running with
tokio-consoleenabled. Release artifacts are now built with the configuration thattokio-consolerequires.
Newer patch available
Upgrade to 3.1.4
You are viewing the 3.1.3 changelog. A newer patch in this release line is available - we recommend running 3.1.4 for the latest fixes and improvements.
3.1.0
Released on May 26, 2026
SurrealDB 3.1.0 is the first minor release on the 3.0 line. It is a stability-focused update with a thorough security pass, many correctness fixes, faster read and scan paths, and a single unified observability pipeline.
It also adds a first-party MCP server for AI agents, a DiskANN vector index alongside HNSW, async functions in Surrealism plugins, full ALTER coverage for every DEFINE statement, distributed trace propagation, a GraphQL overhaul (Apollo naming, cursor pagination, GRAPHQL_ALIAS, and expanded filters), and value::expect for assertions in SurrealQL. In SurrealDB Enterprise, you also get durable audit logging and slow-query telemetry.
The 3.0 → 3.1 catalog and on-disk layouts are unchanged, so existing 3.0.x deployments can upgrade in place. A handful of operational defaults have been tightened, notably RocksDB readahead, which is now 256 KiB. See Improvements for details.
v3.1.0 lands 10 highlight features, 46 broader improvements, 108 bug fixes, and 22 security fixes across the 3.1 series.
Highlights
🤖 First-party Model Context Protocol (MCP) server
SurrealDB now exposes a typed tool surface for AI agents. You can run it locally as a surreal mcp stdio subcommand for IDE integrations, or over HTTP at /mcp behind the existing authentication middleware.
Twelve typed tools:
query,select,create,insert,upsert,update,delete,relate,info,list,use, andrun.read_only_hint/destructive_hint/idempotent_hintannotations so MCP clients can prompt before mutating operations.Self-describing schema resources at
surrealdb://schema/ns/{ns}/db/{db}[/table/{table}].New environment variables:
SURREAL_HTTP_MAX_MCP_BODY_SIZE(default 4 MiB),SURREAL_MCP_QUERY_TIMEOUT_SECS(default 60 s),SURREAL_MCP_MAX_RESULT_BYTES(default 256 KiB),SURREAL_MCP_RUN_MAX_ARGS(default 64), andSURREAL_MCP_PARAMS_MAX_KEYS(default 256).
📊 Unified observability and monitoring pipeline
Observability is now a single OpenTelemetry pipeline instead of separate community Prometheus and SemConv paths.
Every signal is recorded once and routed to many exporters: Prometheus text on
/metrics, OTLP metrics push, OTLP logs push.Metric families are reorganised under the
surrealdb.*scope (statement, query, transaction, RPC, auth, session, network, HTTP, live query, process, storage).The
/metricsendpoint enforces a render-timePUBLIC_METRICSallow-list. Anonymous scrapers see only aggregate process gauges, while root-authenticated operators see the full surface.Tenant context (
namespace/database/user) is carried on every labelled family but filtered out of the public view by family name.New
surrealdb.transaction.retries/surrealdb.transaction.conflictscounters fire from the commit path when the storage engine returns a retryable error.MCP tool dispatch now emits per-tool request metrics alongside GraphQL, HTTP, and WebSocket surfaces.
New environment variable:
SURREAL_PROCESS_METRICS_REFRESH_INTERVAL(default 5 s). Retired:SURREAL_TELEMETRY_NAMESPACE,SURREAL_TELEMETRY_RPC_LIVE_ID. Repurposed:SURREAL_METRICS_ENABLED.This is a breaking change for existing dashboards. See the observability documentation for the migration table and the full new surface.
🧭 Distributed trace-context propagation
Inbound traceparent / tracestate headers are now respected on every transport, so OTEL-aware clients see traces extend continuously through the server.
HTTP:
traceparentis extracted from request headers and parented onto the existingrequestspan. This applies to/rpc,/sql,/key/*,/graphql,/health, and every other route.WebSocket: per-message trace context lives in the RPC envelope (
trace_contextfield, matching the W3C name), so a long-lived connection can serve many independent SDK operations under their own traces instead of bucketing them all under the first one.When
SURREAL_LOG_OTEL_LEVELis unset, the four user-facing spans (request, rpc/call, rpc.execute, executor) surface out of the box. Operators who want deep nested core instrumentation can setSURREAL_LOG_OTEL_LEVEL=trace.Behaviour is unchanged for SDKs that don't yet emit propagation context. Requests without a
traceparentcontinue to produce fresh root spans.
🧭 DiskANN approximate-nearest-neighbour index
A new vector index type sits alongside HNSW under the same KNN query operator, with an overhauled warm-lookup path that benefits both implementations.
New index syntax:
<|K, EF|>queries DiskANN through the same operator used for HNSW.Both DiskANN and HNSW gain
TYPE F16,TYPE U8, andTYPE I8element types, plusDISTANCE INNER_PRODUCTandDISTANCE COSINE_NORMALIZEDmetrics. AddHASHED_VECTORto the index definition if vector-to-document mappings should be stored through vector hashes instead of full vectors.The ANN lookup path was reworked with process-local caches for graph payloads, vector-to-document mappings, and doc-id lookups. KV remains the source of truth. Cache misses fall back to storage, and uncommitted writes never poison the cache.
DEFINE INDEXon an ANN index now drains pending vectors before returning, so a freshly-defined index is immediately usable.Concurrent builds and kNN at release. DiskANN compactions coordinate the cache and graph lock through commit so concurrent
DEFINE INDEX … CONCURRENTLYand KNN queries do not race. HNSW steady-state kNN avoids serialising on graph reload when the in-memory graph already matches KV. IntermittentDiskANN KNN search failederrors from 3.1 betas are addressed (PR #7318).
⚡ Predicate prefilter and scan-path performance work
A series of changes lift the new executor's scan throughput well above 3.0, on both cold and warm data.
Predicate prefilter on every KV scan. Supported
WHEREpredicates are evaluated against raw key/value bytes before the record is decoded, skipping rows that are provably false.EXPLAINreports whether the prefilter applied and which predicates were pushed down.K-way merge for
WHERE col IN [...] ORDER BY other LIMIT N.UnionIndexScannow merges per-IN-list branches by the composite-index suffix column, so an outerLIMITcan stop after ~LIMITrows globally instead of materialising every match.Composite array-element indexes for
CONTAINS/INSIDEandCONTAINSANY/ANYINSIDE. Queries likeWHERE tags CONTAINS 'x' ORDER BY age DESC LIMIT Nnow plan as bounded index scans withLIMITpushed into the scan instead of a full-range read plus heap sort.CONTAINSALL/ALLINSIDEstill keep a residual filter above the union until a follow-up lands.Single-scan
->edge->vertexgraph traversals. Vertex-side adjacency keys now embed the target(table, id)afterRELATE, so plainSELECT ->likes->person FROM person:…can be served by one range scan. Legacy keys remain readable, and re-RELATElazily upgrades the format. Edge tables with non-FULLSELECTpermissions stay on the two-scan path.Lower default RocksDB readahead for better NVMe scan throughput.
SURREAL_ROCKSDB_MAX_AUTO_READAHEAD_SIZEis now 256 KiB on every deployment, replacing RAM-tiered defaults that reached 4 MiB. Larger readahead was often split into multiple IOs with per-IO overhead on typical NVMe devices.Per-row clone elimination on the projection hot path. The streaming executor now moves row payloads through
Computeand projection instead of deep-cloning them per row.Hash-keyed
GROUP BYaggregation. The aggregate state map is now hash-keyed, cutting group-key allocations on large grouped queries while preserving key-sorted output ordering.
💾 Memory optimisations on the value layer
This release includes memory work on the value layer: a small-string-optimised Strand type, a new surrealdb-collections crate with VecMap/VecSet, and reduced per-transaction overhead on the hot path.
Strandis a 24-byte small-string-optimised immutable string type, routed through core hot-path strings:Value::String,Objectkeys,TableName, andRecordIdKey::String.Strandpacks into the same 24 bytes asStringvia arepr(C)SmolStr-style tagged union, with an inline variant storing strings up to 23 bytes with zero heap allocation.A heap variant uses
Arc<str>so clones are a refcount bump rather than a copy.Object entries and
Value::Stringshrink by 8 bytes each, which compounds at document scale.The wire format is unchanged.
Strandserialises identically toStringthroughserde,Revisioned, andstorekey.A companion
Strand::new_staticconstructor lets compile-time-known strings in core skip allocation entirely.
⚙️ Async functions in Surrealism plugins
Plugin authors can mark functions exported through #[surrealism] as async fn and .await host imports directly (storage, network, queries).
Modules can now use async crates like
reqwestandsqlxthat internally depend ontokio::spawnfor connection pooling and timers.The host runtime is initialised once per module instance and persists across invocations on pooled controllers, so there's no per-call setup cost.
The accompanying
surrealism::democrate gains anasync fn fetch_pokemonexample that exercises WASI socket access underallow_net = ["127.0.0.1"].
The broader Surrealism overhaul (WASI Preview 2, WIT-typed host/guest contract, FlatBuffers boundary, attached read-only filesystem, two-engine epoch model) is included in 3.1. See the original beta.1 entry for the full surface change.
🛠 Expanded ALTER coverage
Every DEFINE statement now has a matching ALTER counterpart.
Added
ALTERsupport forEVENT,PARAM,BUCKET,ANALYZER,FUNCTION,USER,ACCESS,CONFIG, andAPI.Each follows the same property-by-property pattern as the existing
ALTER TABLE/FIELD/INDEXstatements.Individual properties on a definition can be modified without re-specifying the whole definition via
DEFINE … OVERWRITE.ALTERresource names and table targets accept bound parameters, matching theDEFINE/REMOVEparameterisation story.ALTER PARAMdoes not supportDROP VALUE(a param without a value is meaningless).ALTER EVENTusesDROP ASYNCto revert to synchronous mode.ALTER ACCESSdoes not allow changing the access type itself.
📦 Durable distributed index build coordination
Concurrently built indexes now keep their build state in the catalog rather than in process memory, and every node coordinates through it.
On multi-node deployments sharing a storage engine, all nodes now agree on which node owns the build, which writes the in-progress index admits, when replay is complete, and when the index becomes query-eligible.
INFO FOR INDEXreflects the durable state.The same change fixes a class of issues where COUNT indexes could overcount when the initial build ran on multiple nodes concurrently.
No SurrealQL surface change.
🔐 Audit logging and slow-query telemetry (Enterprise)
SurrealDB Enterprise ships a new audit-logging and slow-query pipeline alongside the unified observability surface.
A queued, worker-backed
audit_logmodule records authenticated operations to a configurable sink, with a redaction layer that strips sensitive fields before serialisation.Slow-query detection runs alongside the audit pipeline and emits structured records for queries that exceed the configured threshold.
Both pipelines share a size-rotated, SHA-256 hash-chained, optionally fsync'd file sink in append-only
0600mode. Slow-query is essentially the audit pipeline with a duration gate at the front of the observer.Configuration via 13
SURREAL_AUDIT_*and matchingSURREAL_SLOW_QUERY_*environment variables: sink kind, file path, rotation, fsync cadence, hash chaining, SQL inclusion, redaction (tables / regex / literals), queue capacity, overflow strategy, OTLP export.See the observability documentation for the full operator surface.
Improvements
RocksDB and storage engine
Upgraded RocksDB to 11.0.0 and tuned the engine. Enabled prefix extractor for keyspace-aware bloom filters, aligned compaction readahead to disk block size, and enabled blob-file separation by default for large-value workloads. Scaled defaults for constrained environments while large deployments (≥16 GiB memory) keep the previous defaults via tiered scaling.
Tuned RocksDB scans and grouped-commit. Read-only transactions now bypass the merge layer in scan and count paths, removing overhead on every step. Large scans are offloaded to the storage threadpool using improved byte-based sizing. Fixed shutdown and lost-wakeup edge cases in grouped-commit coordinators.
Full-keyspace compaction to bottommost on
ALTER SYSTEM COMPACT.ALTER SYSTEM COMPACTand per-resourceALTER {DATABASE,NAMESPACE,TABLE} COMPACTnow land outputs at L6 with bottommost-level Zstd compaction. Tightened auto-compaction trigger defaults. NewSURREAL_ROCKSDB_PERIODIC_COMPACTION_SECONDS(default 3600, 0 disables) bounds version overhang and tombstone accumulation on cold ranges. Universal compaction is now a first-class option (still off by default) with configurable knobs.Cleaner RocksDB shutdown sequence. Optional full-keyspace compaction (
SURREAL_ROCKSDB_COMPACT_ON_SHUTDOWN, defaultfalse), boundedwait_for_compact(SURREAL_ROCKSDB_SHUTDOWN_WAIT_FOR_COMPACT_SECONDS, default 60 s), and deterministic background-thread teardown. Errors in the shutdown path are logged and continued rather than propagated.Optional RocksDB scan checksum verification. New
SURREAL_ROCKSDB_SCAN_VERIFY_CHECKSUMSflag, defaulttrue(existing behaviour). Setting it tofalseskips CRC32C verification on first-read blocks during scans and counts, trading a small amount of integrity checking for cold-scan throughput on trusted storage.RocksDB user-defined timestamps for versioned reads.
SELECT … VERSION,INFO FOR DB / TABLE VERSION …, graph and reference traversals, theFETCHclause, and inherited inner subqueries are isolated from the LRU transaction cache so historical reads cannot pollute current-time reads.INFO FOR ROOTandINFO FOR NSnow also acceptVERSION.SurrealKV updated to 0.21.2, picking up fixes from (PR #7303). It also raises the maximum memtable size for large transactions.
Memory backend versioning parity.
memory://?versioned=true(andmem://) is parsed correctly at startup.SELECT … VERSIONon a non-versioned memory datastore returns the same error as RocksDB and SurrealKV. Defaultsurreal startno longer enables versioning implicitly.
Performance and executor
Reduced per-transaction overhead on the hot path. Built-in function registry is now shared across transactions instead of rebuilt on every query. Change-feed writer is allocated lazily. Per-transaction cache sizing is tuned for transaction scope rather than defaulting to a large multi-shard allocation.
Capped the scanner's initial batch size using the query
LIMITso that small-LIMITqueries don't fetch more rows than they will ever return.Added a sync fast-path for
PhysicalExprevaluation in scan filters, avoiding async overhead when no IO is required.Avoided cloning the document on
SELECT *projection where the source row can be passed through unchanged.Replaced the global HTTP client cache with a single
HttpClientinstance perDatastore, exposed viaContext::http_client.Configurable scan batch size. Promoted the scan
BATCH_SIZEconstant toSURREAL_SCAN_BATCH_SIZE(default 1000).In-memory performance improvements via SurrealMX switching to ferntree (PR #74), a concurrent B+tree with optimistic lock coupling.
Optimised internal
Valuewire encoding (revision0.23.0). Planner walks can skip or seek without fully deserialising large records. User-visible serialisation through RPC and export is unchanged.
Observability and operations
OTLP exporters now build with TLS (
tls+tls-rootsfeatures), using the platform's native root store viarustls-native-certs.rpc.request_idis emitted as a string identifier. The OTLP runtime is driven by an async runtime so exporters no longer block on a dedicated thread.Made datastore initialisation retry resilient to hung attempts. Each retry attempt is wrapped in its own timeout that increases linearly. The global retry budget is raised from 60 s to 120 s. Failures are logged with a per-task label, and exhaustion returns a descriptive error.
Bounded shutdown background work to mirror the bounded-startup retry behaviour, so a stuck task can no longer indefinitely delay datastore shutdown.
MCP request instrumentation added to the community OpenTelemetry pipeline, with error classification distinguishing client errors from server errors.
Auto-detect TTY for the console log output and respect the
NO_COLORenvironment variable. ANSI escape codes are now only emitted when both stdout and stderr are TTYs.Pluggable live-query broker for clustered deployments.
Datastoreaccepts an optionalMessageBrokerso a composer can forward live-query notifications to the node that owns each subscription when writes commit on a different node than the subscriber's WebSocket.
Functions and SQL surface
Added
value::expectmethod. Assert a predicate on a value via a closure and pass the original value through on success. This is useful in method chains alongsidevalue::chain. On failure, throws with an optional message.Added
REMOVE CONFIG [IF EXISTS] GRAPHQL | API | DEFAULTto mirrorDEFINE CONFIG.Extended GraphQL schema generation to support fields whose type is a literal (record / object / array literal types). Added GraphQL Subscriptions and root-level field comments.
Added
encoding::json::encodeandencoding::json::decodefor serialising SurrealDB values to and from JSON strings, and added support for UTF-16 surrogate-pair escape sequences in the JSON parser (gated on ajson_string_escapesparser setting).Use
serde_jsonto parse external JSON inhttp::*functions so valid JSON responses containing escaped forward slashes (\/) are no longer rejected.Added
--tables-excludeflag tosurreal exportfor excluding specific tables from an export.Allow
surreal validateto read input from stdin, so SurrealQL can be piped in for syntax checking without writing to a file first.Configurable CORS allow list for the HTTP server.
Reject
REMOVE ANALYZERwhile a full-text index uses it, with a clear error naming the offending index and table.REMOVE MODELnow deletes the model file from the configured object store rather than leaving the.surmlartifact orphaned.Tightened numeric input handling on standard library functions. Functions such as
rand::id,array::repeat,string::repeat,duration::from_*, and<bytes> […]now reject negative or out-of-range inputs with a clear error rather than silently wrapping.Strict array-index conversion. Array indexing now treats negatives, NaN, infinities, fractional values, and out-of-range magnitudes as out-of-bounds and returns
NONE. Integer-valued floats and decimals remain valid indices.
SDK and types
Expanded the
surrealdb-typesSDK and derive surface. Added#[surreal(rename_all = "...")]on enums and structs,#[surreal(wrap)]for transparent wrapper types, moreSurrealValueimplementations, serde interop on theValuetype, restored query chaining on theQuerybuilder, and several missing crate exports.Added a builder pattern to
Datastorefor properties that cannot be changed after construction, replacing post-construction setters.Added
surrealdb-collectionsworkspace crate withVecMapandVecSet. These are vec-backed, ordered map and set types for small-collection workloads.ObjectandSetinsurrealdb-corenow use these types.Removed several process-wide configuration globals in
surrealdb-coreso configuration is now carried throughDatastore/Context.SDK tolerates servers denying the
versionRPC during connect (--deny-rpc version).Improved error communication. Type errors in arithmetic / extend operators now report only the type name rather than the raw operand.
GraphQL
This release includes a consolidated GraphQL pass that standardises the auto-generated schema on Apollo conventions, adds Relay-style cursor pagination, and fixes a batch of schema-generation and filter bugs. See GraphQL overview and DEFINE CONFIG GRAPHQL.
Schema naming (Apollo-only)
There is no longer a NAMING DEFAULT|APOLLO switch. Apollo-style names are the only generated shape:
Fetch one record:
person(id: ID!)(was_get_person(id:)).List records:
people(filter, where, order, limit, start, version)with a pluralised table name (wasperson(...)on the list query).Aggregate:
people_aggregate(filter, groupBy, …)(wasperson_aggregate).Mutations:
createPerson/updatePerson/upsertPerson/deletePerson, with bulk formscreatePeople/updatePeople/ etc. (wascreateManyPersonand similar).Object and input type names keep the SurrealQL source name so
record<table>references stay stable. Field names mirror SurrealQL idioms unless overridden.Tables whose names are already plural (
likes,follows) keep the legacy_get_<name>/<verb>Many<Cap>shape so singular and bulk mutation names stay distinct.Two tables that collapse to the same query name abort schema generation with a clear error suggesting
GRAPHQL_ALIAS.
GRAPHQL_ALIAS and GRAPHQL_DEPRECATED
New optional clauses on DEFINE FIELD, DEFINE TABLE, and DEFINE FUNCTION:
Aliases must be valid GraphQL Name tokens. Invalid aliases fall back to the auto-derived name. INFO FOR DB and export round-trip the clauses. Deprecation reasons surface in field descriptions as [Deprecated: …] until the schema builder can emit the @deprecated directive on dynamic fields.
Cursor pagination and batched HTTP
Each table gains a Relay-style connection field, for example peopleConnection(first, after, last, before, filter, where, order, version), returning { edges { cursor node { … } }, pageInfo, totalCount }. Cursors are URL-safe base64 of the record id. first / last default to 20 and cap at 1000. totalCount runs only when the client selects it. Offset pagination via limit / start on the list query remains available.
Posting a JSON array of { query, variables, … } envelopes to the GraphQL HTTP endpoint returns a parallel array of responses (batched requests).
Filters and aggregations
Vector similarity and KNN filters on numeric array fields (for example
array<float>embeddings). Per-field filter inputs acceptsimilarityandnearest(maps to SurrealQL<|k, distance|>). Distance metrics include cosine, Euclidean, Manhattan, Hamming, Jaccard, Chebyshev, and Pearson. Includes the vector-similarity portion of (PR #7312).Full-text search filter via
matcheson string fields, translating to SurrealQL@@(requiresDEFINE INDEX … SEARCH ANALYZER …).Table aggregation via
<plural>_aggregate(filter, groupBy), exposingcount, per-numeric-field min/max/sum/avg, and optionalgroupBy.Function-call filters via
callon every field filter input (string::len,vector::*, userfn::…).idfilters:id: { in: […] },gt/gte/lt/lte, andrange: { from, to, inclusive }. (PR #4555).Relation
countinWHERE: relation fields exposecounton the table filter. For example,people(filter: { sent: { count: { gt: 5 } } })compiles tocount(->sent) > 5. (PR #4554).
Other GraphQL fixes
Schema cache now keys on a content hash of catalog entries, so DDL changes appear on the next GraphQL request. (PR #6942).
TYPE { bar: record<foo> }object literals generate nested GraphQL objects instead of failing at schema build. (PR #7034).array<record<T>>filters and mutation inputs use valid GraphQL names. Nested-object fields no longer leak dots, and mutation inputs for record arrays map to[ID!]. (PR #4999).record<T>mutation inputs areIDeverywhere. Output object types no longer leak into input position.COMPUTEDandREADONLYfields are omitted fromCreate*/Update*/Upsert*inputs. Pre-computedDEFINE TABLE … AS SELECT …views no longer expose create/update/delete mutations.Regex string filters regression-tested (PR #5078).
Added support for aliases for tables, functions, and fields (PR #4537)
Updated the naming convention for GraphQL names (PR #4552).
Bug fixes
surrealdb-core compilation time
The issue causing much longer compile times for surrealdb-core since SurrealDB 3.0 has been identified. 3.1 includes fixes that have vastly reduced compile times when compiling surrealdb-core or adding it as a dependency. More fixes are planned for upcoming 3.1.x releases.
Query Engine
NONE.*,NULL.*, scalar.*, and geometry.*now returnNONEin the new streaming planner, matching the legacy executor, instead of wrapping the value in a single-element array. This fixes the Go SDKcannot decode array into <StructType>error when anoption<record<…>>field wasNONEand the query expanded it with.*. Mixed arrays also now pass non-record elements through unchanged (PR #7143).Fixed
$parentresolving incorrectly inside graphWHEREclauses in nested subqueries.$parentnow refers to the currentSELECT's row rather than an outer subquery's parent.Fixed
FROM ONLYnot being propagated through fused graph lookup chains, which previously produced incorrect results from constrained graph traversals.Fixed
ORDER BY(andWHERE) being silently ignored in subqueries with graph traversal sources likeFROM $parent->edge->target.Fixed bare field paths in a subquery's
FROMclause evaluating toNONEbecause the source operator had no current value in correlated-subquery context.Fixed
$parentnot being bound duringiterator.prepare, which caused correlated subqueries usingFROM $parent->…to see$parentunset while select targets were being planned.Fixed
$parentresolution in materialised view definitions on the streaming path so view materialisation now sees the same parent context as the legacy executor.Fixed
LETbindings in planned bodies not being visible toIF/FORblocks that fall back to the legacy compute path.Fixed
$authand$sessionreturningNONEin queries executed inside a client-side transaction.Fixed duplicate constant values being omitted in the streaming executor (e.g.
false AS isLiked, false AS isLockedcollapsing to a single column).Fixed nested
.*destructure returningNONEfor record links.DestructureField::Allnow fetches the linked record before expanding.Fixed
SELECTalias shadowing the source field of the same name on the projection fast path.Preserved alias idiom structure in streaming-executor projections so that
AS foo.barcontinues to nest into{ foo: { bar: _ } }whileASfoo.barstays as a flat key.Fixed
??(null coalescing) and?:(ternary condition) operator precedence binding tighter than arithmetic.$ctx.limit ?? 2 ** 31 - 1now parses as$ctx.limit ?? (2 ** 31 - 1).Fixed
value::diffandvalue::patchreturningFunction 'value::…' requires async executionwhen invoked via method syntax.Fixed view materialisation crashing when the source
SELECTaliased theidfield.Fixed coercion error messages omitting the position of the offending element on large inputs.
Improved error message when a
LETstatement appears in a non-block expression position.
Query Planner
Fixed
WHEREfilters on indexed fields silently omittingCOMPUTEDfields from evaluation. Indexed scans now resolve computed fields through their pipelines, matching table scans.Fixed records with
NONE/NULLfields being silently excluded fromORDER BYresults when the sort key was on a unique compound index (REBUILD INDEXis required to fix existing indexes).Fixed
SELECT VALUEwithORDER BYignoring the sort order when querying from an array source.Fixed
ORDER BY DESCwith a variableLIMITinside nestedIF/LETblocks inside a function producing wrong results.Fixed
ORDER BYon edge-table queries with record-link source fields returning unsorted output.Fixed
KNN <|K, DISTANCE|>queries bypassing the HNSW index when the distance matches the index configuration.New-executor index analyzer: detects contradictory range bounds and short-circuits to a zero-row scan, merges same-side bounds, short-circuits
WHERE field IN [], and warns when aWITH INDEXhint matches no candidate instead of silently falling back.Indexed
count()/IndexCountScannow refuse plans when theWHEREclause touches a field governed by non-FullSELECT permissions.Composite
CONTAINSANY/ANYINSIDEUnionIndexScanplans no longer strip predicates on fields with restricted SELECT permissions.
Indexes
Made full-text, COUNT and HNSW compaction SSI-safe so concurrent index activity no longer drops work committed after the compaction snapshot. Compaction plans are bounded to fixed-size batches.
Fixed concurrent index conflicts and reduced overhead during full-text concurrent builds.
Fixed cascade-delete index bugs that caused COUNT-index drift, ghost UNIQUE-index entries on relation tables, and phantom UNIQUE-index entries when
IN/OUTrecords were deleted before the relation.Graph and versioned-read permission enforcement. Vertex-delete cascades now run edge deletes with the caller's permissions. The
->edge->vertexsingle-scan fast path is declined when aVERSIONclause is present or the edge table'sSELECTis notFULL. VersionedSELECTnow applies historical field permissions and params.Fixed transient transaction conflicts at commit time dropping in-flight background index work.
Catalog ID sequences now seed safely on the first boot after upgrade, preventing ID collisions with existing catalog entries.
Fixed a memory leak where
DEFINE INDEXwould pin the entireDatastoreuntil process exit (PR #7304).
View
Grouped view writes now emit changefeed deltas and trigger LIVE-query notifications. Previously, writes to a
DEFINE TABLE … AS SELECT … GROUP BY …view skipped these hooks.
Live Queries
Terminate live queries on session invalidation and TTL expiry. The dispatcher also skips notifications for expired sessions.
Remove live-query registrations when the authenticated principal changes on a session (see GHSA-4m82-p8cx-f94j under Security).
REMOVE TABLEnow bumps the datastore live-query cache version, so subscriptions cannot survive a drop andDEFINE TABLEwith the same name.
Functions
value::difffollowed byvalue::patchnow round-trips correctly on top-level scalars (PR #7239).Empty-group sentinels for aggregate functions are now consistent with the corresponding scalar functions.
time::min/time::maxreturnNONEfor empty groups, andmath::variance/math::stddevreturnNaNfor empty groups (PR #7301).Materialised aggregate views now compute
math::powagainst thesumaccumulator andtime::minvia the correct aggregate.Fixed
infinityoutput and added a non-abbreviated constant path so that the constant is preserved on round-trip.Fixed user-duration structure so that durations defined and stored on user definitions round-trip correctly.
Restored the table-membership check on
type::record(rid, table)when arg1 is aRecordIdand arg2 is aString.
Types / SDK
Fixed stack overflow on recursive
SurrealValuetypes so deeply nested or self-referential type definitions no longer crash on encode / decode.Fixed
SurrealValuederive macro emittingr#typeinstead oftypefor raw-identifier struct fields.Fixed a copy-paste bug in
prepare_arraythat made the lookup-optimisation branch dead code for edge traversals inside arrayFROMclauses.Fixed JSON-patch
removeoperation on lists.Fixed
CREATEfailing for fields typed asset<…>.Fixed negative
FETCHarray indices wrapping to a hugeusizeinstead of returningNONE.Local engine
db.runnow validates the function name as an identifier, preventing non-identifier names from being forwarded verbatim.
Storage
Memory
?versioned=enforcement.VERSION/ point-in-time reads onmemory://without?versioned=truenow return the same error as RocksDB and SurrealKV.Fixed long server non-responsiveness when a client cancels
surreal exportmid-stream.Filesystem bucket keys are now preserved exactly as supplied. The
lowercase_pathsURL option for filesystem buckets defaulted totrue, silently folding keys to lowercase on write. The default is nowfalse(PR #7309).file::list({ limit: -1 })now rejects negative limits instead of silently treating-1as unbounded.Tightened the filesystem bucket allowlist.
is_path_allowednow requires UTF-8 validity and a component-boundary prefix match. Object keys containing..segments are rejected.
RPC / WebSocket
Respond with a failure when an in-flight WebSocket request is cancelled rather than silently dropping it.
Propagate CBOR encode errors instead of panicking on the RPC response path.
/keyPOST/PUT/PATCHhandlers now parse the request body as a single SurrealQL value and bind it to$datainstead of running it throughDatastore::executeahead of the fixed CRUD statement./keybodies must be an inert value expression (literals,$param, constants such asmath::PIonly). Executable forms are rejected, closing arbitrary SurrealQL execution on deployments that expose only/key.
Transactions
Surface unified
Cannot COMMIT: …errors when an explicitCOMMITafter a previous statement abort fails, instead of returning only the first statement error.
Auth
Fixed
idresolving toNONEinside a table'sFOR select WHERE … permissionclause when a record was created and selected in the same statement.Fixed user-defined function
PERMISSIONSclauses being evaluated for system-level users (root, namespace, database).Live-query computed-field permissions are now applied after the computed fields are populated.
ALTER APInow recomputes the caller's IAM envelope from current privileges.GraphQL
fn_*resolvers now read the session from the per-request GraphQL context instead of closing over the schema-generation-time session.USE NS/USE DBno longer auto-creates namespaces or databases without the same authorization asDEFINE NAMESPACE/DEFINE DATABASE.
Events
Fixed events defined on a vertex table incorrectly firing for related edge-table records during cascade deletion.
Logging
Fixed JSON log format being unused even when configured.
Fixed ANSI escape codes leaking into JSON log output and added per-stream TTY detection for mixed-redirect scenarios.
CLI
Prevent panic on systems with restricted
/procaccess whereSystem::total_memory()returns 0.--client-ip=forwarded. Parses the RFC 7239Forwardedheader and uses thefor=identifier from the first forwarded-element as the request client IP.FIPS build option (Enterprise).
surrealdb-servergains afipsCargo feature that routes TLS through the AWS-LC FIPS module.
Imports / Exports
Fixed re-importing an exported database failing with "field already exists" errors. Export now emits
DEFINE FIELD OVERWRITE.
GraphQL
Reserved-word and backtick-quoted field names no longer leak into the generated schema, fixing introspection failures in strict clients.
Nested array types deeper than three levels now collapse to the
JSONscalar in the generated schema instead of exceeding the standard introspection limit.Pre-execution HTTP errors now return a spec-compliant JSON body with
Content-Type: application/json, instead of a plain-text 400.
Security
This release addresses a substantial batch of issues surfaced by SurrealDB's internal security review process and external reviewers. Most are reachable only by an authenticated caller and require existing privileges. A couple are pre-auth DoS or capability-bypass conditions. The fix for each of these issues ships in v3.1.0 and is enabled by default. No configuration is required.
HTTP
/rpcsessionsmethod leaks attached session UUIDs [High] - GHSA-5qfp-32cf-69jhHTTP
/rpcsession race condition allows privilege escalation [High] - GHSA-4vgr-h27g-cf9pDenial of Service in JSON parser due to nested objects [High] - GHSA-q729-696q-g9pq
Unauthenticated remote DoS via malformed RPC
usecall [High] - GHSA-wjjj-24cx-f28gDenial of Service via nested type annotations [Moderate] - GHSA-q8qp-67f9-wr3f
Scraping a table with no available permissions to the current auth level [Moderate] - GHSA-98fx-66cf-fc7c
Graph traversal bypasses table SELECT permissions [Moderate] - GHSA-vjjx-rfw4-rmfc
Authorization bypass via composite record-id paths [Moderate] - GHSA-6vg3-hgrw-p5gf
Malicious LIVE query can block writes on the watched table [Moderate] - GHSA-4v76-cw68-4vc9
Authorization bypass in
KILLstatement allows termination of other users' live queries [Moderate] - GHSA-gcwr-5mrf-fvchPre-auth memory amplification via unbounded
/sqlWebSocket frames [Moderate] - GHSA-65rj-r9fh-jp2vLIVE query subscriptions survive session state changes [Moderate] - GHSA-4m82-p8cx-f94j
Field-level SELECT permissions leaked via arithmetic error messages [Moderate] - GHSA-6g9v-7gq3-p2c6
Field-level SELECT permissions bypassed via JSON Patch
copy/move[Moderate] - GHSA-fpxg-5xmv-922mRELATEoverwrites existing edge records withoutUPDATEpermission [Moderate] - GHSA-f82j-v89j-mf86LIVE subscribers can read hidden records via captured
$value/$before/$after/$event[Moderate] - GHSA-6wqw-vhfr-9999Port-specific
--deny-netrules bypassed on HTTP redirect [Moderate] - GHSA-97vg-427p-8hx5Implicit namespace/database creation via
USEbypasses DEFINE auth [Moderate] - GHSA-wp87-mgvq-5j93Field-level SELECT permissions bypassed via indexed COUNT fast paths [Moderate] - GHSA-c8jx-96c9-8xrp
Edge
PERMISSIONS FOR deletebypassed when a connected node is deleted [Moderate] - GHSA-whwg-vh4f-pmmfDEFINE ACCESS … ALGORITHM ES512silently downgraded to ES384 [Moderate] - GHSA-fwg2-gr34-q3w8
Breaking changes
Catalog and KV on-disk layouts are unchanged from 3.0.x, so you can upgrade in place. The items below are behaviour or default changes that can affect clients, dashboards, or SurrealQL that relied on previous (often undocumented) semantics.
HTTP /key request bodies
POST, PUT, and PATCH on /key/:table and /key/:table/:id no longer treat the request body as SurrealQL to execute. The body is parsed as a single SurrealQL value and bound to $data only. The value must be inert: literals, $param references, and constants only. Function calls, idioms, statements, and parenthesised executable forms are rejected.
If you previously sent bodies such as time::now(), fn::…(), or multi-statement SurrealQL through /key expecting server-side evaluation, move that logic to POST /sql or RPC instead, or compute values in the client and send JSON literals.
Unified observability metrics
The community metrics pipeline was reorganised under the surrealdb.* scope with a new /metrics allow-list. Existing Prometheus dashboards and alerts that scrape metric names from 3.0.x need updating. See the observability documentation for the migration table.
RocksDB readahead default
SURREAL_ROCKSDB_MAX_AUTO_READAHEAD_SIZE now defaults to 256 KiB on every deployment instead of RAM-tiered values up to 4 MiB. Override the variable if you tuned scan behaviour around the old defaults.
Filesystem bucket key casing
For filesystem-backed buckets, the lowercase_paths URL option now defaults to false. Keys are stored with the casing you supply. Set ?lowercase_paths=true explicitly if you depend on the previous default of folding paths to lowercase on write.
GraphQL Apollo schema naming
The auto-generated GraphQL schema now follows a single Apollo-style naming convention (there is no NAMING config). Existing clients must update query and mutation names:
| Before (3.0.x) | After (3.1.0) |
|---|---|
_get_person(id: …) | person(id: …) |
person(filter: …) (list) | people(filter: …) (pluralised) |
person_aggregate(…) | people_aggregate(…) |
createManyPerson(…) | createPeople(…) |
Regenerate introspection, client stubs, and saved queries after upgrade. Use GRAPHQL_ALIAS on DEFINE TABLE, DEFINE FIELD, or DEFINE FUNCTION when the default pluralisation collides or when camelCase names are required. See GraphQL under Improvements for the full surface.
GraphQL object and input types
GeoJSON output and input geometry types now expose coordinates as the JSON scalar instead of deeply nested Float list types. The wire shape at query time is still nested arrays of numbers. Only the introspected GraphQL type changes. Clients that generated queries or validators from the old nested-float schema must be regenerated.
--allow-net now also gates resolved private and special-use IPs
When a hostname permitted by --allow-net resolves to a private or special-use IP, the resolved IP must also be covered by an --allow-net entry. Public IPs are unaffected and the check is skipped under --allow-net all. To allow internal traffic by hostname, for example --allow-net internal.example.com resolving to 10.0.5.42, add the range: --allow-net internal.example.com,10.0.5.0/24.
Ranges checked: IPv4 loopback (127.0.0.0/8), RFC 1918 (10/8, 172.16/12, 192.168/16), link-local (169.254.0.0/16), shared (100.64.0.0/10), broadcast, and unspecified. For IPv6: loopback (::1), unspecified (::), unique-local (fc00::/7), and link-local (fe80::/10). IPv4-mapped IPv6 is canonicalised first.
Newer patch available
Upgrade to 3.1.4
You are viewing the 3.1.0 changelog. A newer patch in this release line is available - we recommend running 3.1.4 for the latest fixes and improvements.
3.1.0-beta.3
Released on May 14, 2026
This is the third beta in the 3.1 series. It adds DiskANN as a second approximate-nearest-neighbour index type alongside HNSW (with an end-to-end overhaul of the ANN warm-lookup path that benefits both implementations), brings the new executor's WHERE-clause prefilter to every KV scan with EXPLAIN visibility, and lets Surrealism plugins write async fn directly. The full cumulative scope from v3.0.5 is documented in the v3.1.0-beta.1 entry; the items below describe only what changed between beta.2 and beta.3.
Highlights
DiskANN approximate-nearest-neighbour index
A new vector index type sits alongside HNSW under the same KNN query operator, with an overhauled warm-lookup path that benefits both implementations.
New index syntax:
<|K, EF|>queries DiskANN through the same operator used for HNSW.Both DiskANN and HNSW gain
TYPE F16,TYPE U8, andTYPE I8element types, plusDISTANCE INNER_PRODUCTandDISTANCE COSINE_NORMALIZEDmetrics. AddHASHED_VECTORto the index definition if vector-to-document mappings should be stored through vector hashes instead of full vectors.The RocksDB-backed ANN lookup path was reworked end-to-end. The dominant cost on warm-lookup profiles - scanning empty pending-update ranges - is gone; in its place sit process-local caches for graph payloads, vector-to-document doc sets, and doc-id-to-record-id mappings. KV stays the source of truth: cache misses fall back to existing reads, write paths evict, and uncommitted writes never poison the positive doc-id cache.
Lookup result materialisation is batched, and graph candidates resolve by
ElementIdfirst so warm lookups skip the previous re-read of candidate vectors.DEFINE INDEXon an ANN index now drains pending vectors before returning, so a freshly-defined index is immediately usable.The pending-state skip is distributed-safe: new
!dp(DiskANN) and!ht(HNSW) guard keys are written conditionally by compaction, so on multi-node deployments lookups only skip pending scans when KV state confirms the range is empty.
Predicate prefilter for KV scans
The new executor's KV scan operator evaluates supported WHERE predicates against the raw RocksDB key/value pair before the record is decoded. Rows whose predicate is provably false from raw bytes are rejected before revision decode, schema enforcement, projection, and downstream operators run.
Supported predicate shapes: root-level equality (
a = …),IN/NOT INagainst literal arrays, byte-levelCONTAINS/CONTAINSANY/CONTAINSALL, simple unaryNOT, conjunctions/disjunctions of the above, and nested shared-prefix accesses such asouter.x AND outer.y.Previously gated behind the
SURREAL_PREDICATE_PREFILTERenvironment variable; now always on, with the legacy parallel path removed.EXPLAINreports whether the prefilter applied and which predicates were pushed down, so operators can confirm the optimisation is firing.Applies on top of every scan operator that touches RocksDB - base table, indexed, unique compound, and full-text - without configuration.
Async functions in Surrealism plugins
Plugin authors can mark functions exported through #[surrealism] as async fn and .await host imports directly (storage, network, queries).
Modules can now use async crates like
reqwestandsqlxthat internally depend ontokio::spawnfor connection pooling and timers.The host runtime is initialised once per module instance and persists across invocations on pooled controllers, so there's no per-call setup cost.
The accompanying
surrealism::democrate gains anasync fn fetch_pokemonexample that exercises WASI socket access underallow_net = ["127.0.0.1"].This is a stopgap until WASM Preview 3 / component-model async lands upstream, at which point the embedded executor becomes unnecessary.
Improvements
Durable, cluster-wide index build coordination. Concurrently built indexes now keep their build state in the catalog rather than in process memory, and every node coordinates through it. On multi-node deployments sharing a storage engine, all nodes now agree on which node owns the build, which writes the in-progress index admits, when replay is complete, and when the index becomes query-eligible.
INFO FOR INDEXreflects the durable state. The same change closes a class of issues where COUNT indexes could overcount when the initial build ran on multiple nodes concurrently. No SurrealQL surface change.Lower default RocksDB readahead for better NVMe scan throughput. The default
SURREAL_ROCKSDB_MAX_AUTO_READAHEAD_SIZEis now 256 KiB on every deployment, replacing the previous RAM-tiered defaults that reached 4 MiB. Most NVMe block layers cap a single device IO around/sys/block/<device>/queue/max_sectors_kb(typically 128–512 KiB), so larger readahead values were being split into multiple IOs with per-IO overhead and over-prefetching. On a representative NVMe seek-random benchmark the per-machine optimum was 128 KiB at 578 ops/sec / 640 MB/s; 4 MiB readahead was ~60% slower. Operators with non-standard storage can still override via the environment variable.Optional RocksDB scan checksum verification. New
SURREAL_ROCKSDB_SCAN_VERIFY_CHECKSUMSflag, defaulttrue(existing behaviour). Setting it tofalseskips CRC32C verification on first-read blocks during scans and counts; cached blocks were never re-checksummed either way. Trades a small amount of integrity checking for cold-scan throughput on trusted storage.SurrealKV updated to 0.21.2 picking up the upstream fixes from surrealdb/surrealdb#7303.
Bug fixes
[View]Grouped view writes now emit changefeed deltas and trigger LIVE-query notifications. Previously, writes to aDEFINE TABLE … AS SELECT … GROUP BY …view skipped the changefeed-emit and live-query-notify hooks because the aggregate metadata is written directly throughtx.set_record- soSHOW CHANGESand live-query subscribers missed updates to grouped views. The view-write lifecycle now matches the regular document pipeline.[Query Engine]NONE.*,NULL.*, scalar.*, and geometry.*now returnNONEin the new streaming planner - matching the legacy executor - instead of wrapping the value in a single-element array. Fixes the Go SDKcannot decode array into <StructType>error that appeared when anoption<record<…>>field wasNONEand the query expanded it with.*. Mixed arrays also now pass non-record elements through unchanged, so[1, record:r, "x"].*returns[1, {fetched}, "x"]rather than[[1], {fetched}, ["x"]](fixes surrealdb/surrealdb#7143).[Functions]value::difffollowed byvalue::patchnow round-trips correctly on top-level scalars.value::diffof two scalars produced a JSON Patch with the empty pointer""as its path; the patch parser was treating that as a single-empty-segment path rather than the document root, sovalue::patchsilently no-op'd and returned the original value (fixes surrealdb/surrealdb#7239).[Functions]Empty-group sentinels for aggregate functions are now consistent with the corresponding scalar functions:time::minandtime::maxreturnNONEfor empty groups (previously returned the chronoMAX_UTC/MIN_UTCplaceholders, which surfaced to clients asd'+262142-12-31T23:59:59.999999999Z').math::varianceandmath::stddevreturnNaNfor empty groups (previously returned0.0, which conflated "no data" with "all values identical"). A single-element group still returns0.0.Fixes surrealdb/surrealdb#7301.
[Functions]Materialised aggregate views now computemath::powagainst thesumaccumulator (previously usedcount, returning the wrong magnitude) andtime::minvia the correctDatetimeMinaggregate (previously usedDatetimeMax, returning the maximum).[Indexes]Catalog ID sequences now seed safely on the first boot after upgrade. The new per-node batch-based ID generator initialised at0when its state key was missing, so the first namespace, database, table, or index created after an upgrade could reuse an existing ID and collide with the live catalog entry. The sequence loader now scans the existing catalog and starts frommax(existing_id) + 1whenever the state key isn't present.
Newer patch available
Upgrade to 3.1.4
You are viewing the 3.1.0-beta.3 changelog. A newer patch in this release line is available - we recommend running 3.1.4 for the latest fixes and improvements.
3.1.0-beta.2
Released on May 8, 2026
This is the second beta in the 3.1 series and continues the stabilisation focus, with a new audit-logging and slow-query pipeline in the Enterprise product. The full cumulative scope from v3.0.5 is documented in the v3.1.0-beta.1 entry; the items below describe only what changed between beta.1 and beta.2.
Items tagged (Enterprise) on a section heading or [Enterprise] on a bullet refer to the SurrealDB Enterprise product. Everything else applies to community.
Highlights
Audit logging and slow-query telemetry (Enterprise)
A new audit-logging pipeline with configurable sinks, redaction, and slow-query detection has been added to Enterprise.
A queued, worker-backed
audit_logmodule records authenticated operations to a configurable sink, with a redaction layer that strips sensitive fields before serialisationSlow-query detection runs alongside the audit pipeline and emits structured records for queries that exceed the configured threshold
Configuration is exposed through new
SURREAL_AUDIT_*andSURREAL_SLOW_QUERY_*environment variables (seecrates/enterprise/src/cnf/audit.rsandcrates/enterprise/src/cnf/slow_query.rs)The audit queue, observer, record, redact, sink, and worker modules sit under
crates/enterprise/src/observe/audit_log/
Improvements
Extended the community OpenTelemetry pipeline with MCP request instrumentation, error classification, and additional protocol-level metrics (#67)
A new
observe/error_class.rsmodule categorises errors emitted by the executor, KV transactions, and the RPC and WebSocket layers, so dashboards can distinguish client errors from server errors without parsing message stringsThe MCP server now emits per-tool request metrics alongside the GraphQL and HTTP/WebSocket surfaces via a new
observe/mcp_adapter.rsGraphQL, HTTP-tower, and WebSocket instrumentation surfaces are widened to record more of the request lifecycle
Bounded shutdown background work to mirror the bounded-startup retry behaviour introduced in beta.1 (#41)
Shutdown of long-running background tasks now uses
tokio::time::timeout_atagainst the global deadline, so a stuck task can no longer indefinitely delay datastore shutdownAdded
is_query_cancelledandis_query_timedouthelpers inerr/mod.rsso the shutdown path can distinguish expected cancellation from real errors
Enabled TLS for the OTLP exporters and aligned
rpc.request_idwith the OpenTelemetry semantic conventions (#53)The OTLP push exporter now builds with the
tlsandtls-rootsfeatures and uses the platform's native root store viarustls-native-certsrpc.request_idis emitted as a string identifier rather than the previous binary form, matching the OTelrpc.*conventions used by external collectorsThe OTLP runtime is now driven by an async runtime so exporters no longer block on a dedicated thread
Removed
LazyLockfrom the hot path to eliminate a small but measurable per-request synchronisation cost (#48)Avoided emitting empty reference range deletes during the reference purge so the storage layer no longer pays for a no-op range delete on tables with no references (#49)
Upgraded the Rust toolchain to 1.95 across community (#57) and
[Enterprise](#81).
Bug fixes
[Query Engine]Fixed coercion error messages omitting the position of the offending element, which madeexpected array<int>/expected tupleerrors unactionable on large inputs. Coerce and cast paths inval/value/convert/{coerce,cast}.rsnow thread the element index through the error (#52, resolves #5677).[Indexes]Fixed transient transaction conflicts at commit time dropping in-flight background index work. The background index runner now distinguishes recoverable conflicts from fatal errors and retries against fresh state, with the retry harness shared acrosskvs/index.rsandkvs/ds.rs(#69).[Indexes]FixedVecMap::appendextending the map in a way that could leave duplicate keys behind. Thesurrealdb-collectionscrate'sVecMapnow de-duplicates and re-sorts on append (#59).
Newer patch available
Upgrade to 3.1.4
You are viewing the 3.1.0-beta.2 changelog. A newer patch in this release line is available - we recommend running 3.1.4 for the latest fixes and improvements.
3.1.0-beta.1
Released on May 1, 2026
This is the first beta in the 3.1 series. The focus is bug fixing and stabilisation of the 3.0 line, with a number of substantial additions on top of the fixes that already shipped in v3.0.1–v3.0.5.
Highlights
Bug fixes and stabilisation
This release fixes over 40 community-reported bugs.
A first-party Model Context Protocol (MCP) server
Exposed as a surreal mcp stdio subcommand for local IDE integrations, and as an HTTP /mcp endpoint guarded by the existing authentication middleware.
The tool surface includes typed
query,select,create,insert,upsert,update,delete,relate,info,list,use, andruntoolsread_only_hint/destructive_hintannotations so MCP clients can prompt the user before mutating operationsRequest bodies are bound as typed
Variablesrather than string-interpolatedIdentifiers are validated against a strict allow-list
Per-request subject verification rejects cross-session credential reuse
New environment variables:
SURREAL_HTTP_MAX_MCP_BODY_SIZE(default 4 MiB),SURREAL_MCP_QUERY_TIMEOUT_SECS(default 60 s),SURREAL_MCP_MAX_RESULT_BYTES(default 256 KiB),SURREAL_MCP_RUN_MAX_ARGS(default 64), andSURREAL_MCP_PARAMS_MAX_KEYS(default 256)
A unified observability and monitoring pipeline built on OpenTelemetry
Overhauled the observability and monitoring layer onto a single OpenTelemetry pipeline.
The previous community Prometheus path and the parallel
OtelObserverSemConv pipeline have been collapsed onto oneSdkMeterProviderand oneSdkLoggerProviderEvery signal is recorded once and routed to many exporters: Prometheus text on
/metrics, OTLP metrics push, OTLP logs pushMetric families are reorganised under the
surrealdb.*scope (statement, query, transaction, RPC, auth, session, network, HTTP, live query, process, storage)The
/metricsendpoint enforces a render-timePUBLIC_METRICSallow-list: anonymous scrapers see only aggregate process gauges, while root-authenticated operators see the full surfaceTenant context (
namespace/database/user) is carried on every labelled family but filtered out of the public view by family nameNew environment variable:
SURREAL_PROCESS_METRICS_REFRESH_INTERVAL(default 5 s)Retired:
SURREAL_TELEMETRY_NAMESPACE,SURREAL_TELEMETRY_RPC_LIVE_IDRepurposed:
SURREAL_METRICS_ENABLEDThis is a breaking change for existing dashboards; see
doc/OBSERVABILITY.mdfor the migration table
Memory optimisations on the value layer
A round of memory work on the value layer including a 24-byte small-string-optimised Strand type, a new surrealdb-collections crate with VecMap/VecSet, and a per-transaction overhead reduction on the hot path.
Introduced
Strand, a 24-byte small-string-optimised immutable string type, routed through every core hot-path string:Value::String,Objectkeys,TableName, andRecordIdKey::StringStrandpacks into the same 24 bytes asStringvia arepr(C)SmolStr-style tagged union, with an inline variant storing strings up to 23 bytes with zero heap allocationA heap variant uses
Arc<str>so clones are a refcount bump rather than a copyObject entries and
Value::Stringshrink by 8 bytes each, which compounds at document scaleThe wire format is unchanged:
Strandserialises identically toStringthroughserde,Revisioned, andstorekeyA companion
Strand::new_staticconstructor lets compile-time-known strings in core skip allocation entirely
Significant RocksDB tuning across the engine
Substantial performance improvements through engine tuning including RocksDB 11.0.0 upgrade, prefix extractor, blob-file separation by default, and scaled defaults for constrained environments.
RocksDB upgraded to 11.0.0 with enabled prefix extractor for keyspace-aware bloom filters
Aligned the compaction readahead size to
max_sector_kbon disk (currently 256 KiB on Cloud)Enabled blob-file separation by default for large-value workloads
Scaled defaults for constrained environments (the SurrealDB Cloud free tier sees disproportionate memory and disk pressure on the previous mid-sized defaults)
Large deployments (≥16 GiB memory) keep the previous defaults via tiered scaling; small deployments get appropriately sized buffers, caches, and compaction settings out of the box
Expanded ALTER coverage
Every DEFINE statement now has a matching ALTER counterpart.
Added
ALTERsupport forEVENT,PARAM,BUCKET,ANALYZER,FUNCTION,USER,ACCESS,CONFIG, andAPIEach follows the same property-by-property pattern as the existing
ALTER TABLE/FIELD/INDEXstatementsIndividual properties on a definition can be modified without re-specifying the whole definition via
DEFINE … OVERWRITEALTER PARAMdoes not supportDROP VALUE(a param without a value is meaningless)ALTER EVENTusesDROP ASYNCto revert to synchronous modeALTER ACCESSdoes not allow changing the access type itself (record / JWT / bearer), only durations, theAUTHENTICATEclause, and comment
Improvements
Added
REMOVE CONFIG [IF EXISTS] GRAPHQL | API | DEFAULTto mirrorDEFINE CONFIG, providing a way to deleteGRAPHQL,API, andDEFAULTconfigurations.Extended GraphQL schema generation to support fields whose type is a literal (record / object / array literal types), so they now appear in the generated GraphQL schema rather than being silently omitted.
Added the
surrealdb-collectionsworkspace crate withVecMapandVecSetVec-backed, ordered, insertion-order-preserving map and set types
Tuned for the small-collection workloads that dominate SurrealDB's value layer
ObjectandSetinsurrealdb-corenow use these typesLinear scan for small slices, skipped re-sort when inputs are already ordered, and a tuned linear-vs-binary search threshold
User-facing semantics are unchanged
Upgraded RocksDB to 11.0.0 and tuned the engine
Enabled prefix extractor for keyspace-aware bloom filters
Aligned the compaction readahead size to
max_sector_kbon disk (currently 256 KiB on Cloud)Enabled blob-file separation by default for large-value workloads
Scaled defaults for constrained environments (the SurrealDB Cloud free tier sees disproportionate memory and disk pressure on the previous mid-sized defaults)
Large deployments (≥16 GiB memory) keep the previous defaults via tiered scaling; small deployments get appropriately sized buffers, caches, and compaction settings out of the box
Reduced per-transaction overhead on the hot path
FunctionRegistry::with_builtinsis now built once per datastore and shared across transactions instead of being rebuilt on every query (was ~22% of CPU on a CREATE-heavy profile)The change-feed
Writeris allocated lazily so transactions that touch no changefeed-enabled tables no longer pay for aDashMap<ChangeKey, TableMutations>and its dropThe per-transaction
quick_cachenow uses a single shard sized for transaction-scope rather than theavailable_parallelism() * 4default, eliminating the 256CacheShardallocations that used to happen on everyTransaction::newon a 64-core host
Updated SurrealKV to 0.21.1 and raised the maximum memtable size for large transactions, allowing larger batches to be inserted without flushing mid-transaction.
Tuned RocksDB scans and grouped-commit
Read-only transactions now bypass the
BaseDeltaIteratormerge layer incount,keys,keysr,scan, andscanr, which removes overhead on every stepThe offload heuristic for moving large scans to the storage threadpool now uses byte-based sizing (including skip work) and handles
BytesOrCountcorrectlyThe grouped-commit coordinators (RocksDB and SurrealKV) had shutdown and lost-wakeup edge cases that could stall threads or callers; both are fixed
Capped the scanner's initial batch size using the query
LIMITso that small-LIMITqueries don't fetch more rows than they will ever return.Added a sync fast-path for
PhysicalExprevaluation in scan filters, avoiding the async overhead for the common case where no IO is required.Avoided cloning the document on
SELECT *projection where the source row can be passed through unchanged, reducing allocator pressure on read-heavy workloads.Replaced the global HTTP client cache (which keyed on a hash of capabilities) with a single
HttpClientinstance perDatastore, exposed viaContext::http_clientRemoves process-wide global state
Simplifies test setup
Removes per-request hashing
Context::backgroundis renamed toContext::new_testand is now test-onlyContext::newis renamed toContext::new_childto make its sub-context role explicit
Made datastore initialisation retry resilient to hung attempts
Each retry attempt is now wrapped in its own per-attempt
tokio::time::timeoutthat increases linearly (10 s, 20 s, 30 s, …)A single hung attempt against a slow or unresponsive storage backend can no longer silently consume the entire retry budget
Per-attempt timeouts and back-off sleeps are capped to the remaining global budget
The global retry budget is raised from 60 s to 120 s
Failures are now logged with a per-task label so the failed operation is identifiable
Exhaustion returns a descriptive
Internalerror instead of re-raising the lastTransactionConflict
Added RocksDB user-defined timestamps and threaded the
VERSIONclause through every internal schema lookupVersioned reads (
SELECT … VERSION d'…',INFO FOR DB / TABLE VERSION …) are now isolated from the LRU transaction cache so historical reads cannot pollute current-time readsThe
VERSIONclause is now propagated through graph and reference traversals, theFETCHclause, automatic record dereferences during field access, and inherited by inner subqueries when the outerSELECTcarries a versionINFO FOR ROOTandINFO FOR NSnow also acceptVERSIONRocksDB gains a custom comparator that stores 8-byte LE timestamps as key suffixes (newer versions ordered before older), with HLC commit timestamps assigned at commit time and
ReadOptions::set_timestampfor point-in-time reads
Overhauled the Surrealism (WASM plugin) stack to v0.2.0 and migrated to WASI Preview 2 (Component Model)
Modules now target
wasm32-wasip2and a WIT interface (surrealism/wit/surrealism.wit) defines the host/guest contractNew
#[surrealism]attribute options:writeable(lets the planner choose appropriate transaction modes),comment(description visible insurreal module infoandINFO FOR DB STRUCTURE),init(one-time init hook with access to host imports), and namespaced module exports that produce prefixed names likemath::addData crossing the host/guest boundary now serialises via FlatBuffers
Modules can declare an attached read-only filesystem (
[attach] fs = "fs"insurrealism.toml) bundled into the.surliarchive and mounted at/inside the WASM sandboxThe host runtime gets a two-engine epoch model - a guarded engine with epoch interruption for timeout enforcement, and a fast engine for trusted compute-heavy modules - selected via
strict_timeoutinsurrealism.toml
Added a builder pattern to
Datastorefor properties that cannot be changed after construction, replacing the previous post-construction settersMakes invalid configurations harder to express
Lets the language-test suite exercise more configuration surfaces
The language-test harness has been overhauled in the same change
Expanded the
surrealdb-typesSDK and derive surfaceAdded
#[surreal(rename_all = "...")]on enums and structs and honoured per-variant#[surreal(rename = "...")](previously parsed only for fields)Added
#[surreal(wrap)]for transparent wrapper typesAdded more
SurrealValueimplementations for standard-library types and aNaiveDateimplementationGeneralised the
Cowand&strSurrealValueimplementationsAdded serde interop on the
ValuetypeRestored query chaining on the
QuerybuilderAdded several missing crate exports
Auto-detect TTY for the console log output and respect the
NO_COLORenvironment variableANSI escape codes are now only emitted when both stdout and stderr are TTYs
Fixes garbled output in Docker containers, Kubernetes pods, and log shippers like Kibana and GKE Log Explorer
Allow
surreal validateto read input from stdin, so SurrealQL can be piped in for syntax checking without writing to a file first.Removed several process-wide configuration globals in
surrealdb-coreso configuration is now carried throughDatastore/Contextrather than read from static state, simplifying embedding and testing.
Bug fixes
[Query Engine]Fixed$parentresolving incorrectly inside graphWHEREclauses in nested subqueries;$parentnow refers to the currentSELECT's row rather than an outer subquery's parent.[Query Engine]FixedFROM ONLYnot being propagated through fused graph lookup chains, which previously produced incorrect results from constrained graph traversals.[Query Engine]FixedORDER BY(andWHERE) being silently ignored in subqueries with graph traversal sources likeFROM $parent->edge->target. The streaming source operator now resolvesRecordIdvalues to full documents so downstreamFilter,Sort,Group, andSplitoperators have the fields they need.[Query Engine]Fixed bare field paths in a subquery'sFROMclause (e.g.FROM data.fileswheredatais a record link on the outer document) evaluating toNONEbecause the source operator had no current value in correlated-subquery context.[Query Engine]Fixed$parentnot being bound duringiterator.prepare, which caused correlated subqueries usingFROM $parent->…(including nestedCREATE,UPDATE, andDELETE) to see$parentunset while select targets were being planned.[Query Engine]Fixed$parentresolution in materialised view definitions on the streaming path so view materialisation now sees the same parent context as the legacy executor.[Query Engine]FixedLETbindings in planned bodies not being visible toIF/FORblocks that fall back to the legacy compute path; the legacyFrozenContextis now refreshed after a plannedLETbody runs.[Query Engine]Fixed$authand$sessionreturningNONEin queries executed inside a client-side transaction; the session is now correctly attached to the per-statement context.[Query Engine]Fixed duplicate constant values being omitted in the streaming executor (e.g.false AS isLiked, false AS isLockedcollapsing to a single column). The expression registry's de-duplication key now includes the alias.[Query Engine]Fixed nested.*destructure returningNONEfor record links;DestructureField::Allnow fetches the linked record before expanding.[Query Engine]FixedSELECTalias shadowing the source field of the same name on the projection fast path.[Query Engine]Preserved alias idiom structure in streaming-executor projections so thatAS foo.barcontinues to nest into{ foo: { bar: _ } }whileASfoo.barstays as a flat key - both the executor projection andGROUP BYalias paths previously stringified the alias and lost the backtick distinction.[Query Engine]Fixed??(null coalescing) and?:(ternary condition) operator precedence binding tighter than arithmetic.$ctx.limit ?? 2 ** 31 - 1now parses as$ctx.limit ?? (2 ** 31 - 1), matching JavaScript / C# / Kotlin / Swift / PHP semantics; previously the operators bound above**,*,/,+, and-, which made??nearly useless without explicit parentheses.[Query Engine]Fixedvalue::diffandvalue::patchreturningFunction 'value::…' requires async executionwhen invoked via method syntax ({a: 1}.diff({a: 2})). The async-vs-sync check now matches the behaviour of the full-path call.[Query Engine]Fixed view materialisation crashing when the sourceSELECTaliased theidfield; initialisation now falls back to scanning for anyRecordIdwhen the"id"key is absent.[Query Engine]Fixed references being created against a target table even when the originatingCREATE/INSERT/RELATE/UPSERT/UPDATEfailed itsCREATEpermission check; references are now cleaned up on the permission-denied error path.[Query Planner]FixedWHEREfilters on indexed fields silently omittingCOMPUTEDfields from evaluation.IndexScan,FullTextScan, andKnnScannow resolve field state and process computed fields through their pipelines, matchingTableScanandUnionIndexScan.[Query Planner]Fixed records withNONE/NULLfields being silently excluded fromORDER BYresults when the sort key was on a unique compound index; unique indexes now storeNONE/NULLtuples in the non-unique key format so they remain visible to scans (REBUILD INDEXis required to fix existing indexes).[Query Planner]FixedSELECT VALUEwithORDER BYignoring the sort order when querying from an array source; theVALUEprojection is now applied after sorting and pagination instead of before.ORDER BYcan also reference aSELECT VALUEalias.[Query Planner]FixedORDER BY DESCwith a variableLIMITinside nestedIF/LETblocks inside a function producing wrong results.[Query Planner]FixedORDER BYon edge-table queries with record-link source fields likein.creationDatereturning unsorted output. The sort path now routes resolved alias expressions through theComputeoperator (which can fetch linked records) and restricts the synchronousFieldPathshortcut to single-part paths where record-link traversal is impossible.[Query Planner]FixedKNN <|K, DISTANCE|>queries bypassing the HNSW index;eval_hnsw_knnnow matches theK(k, d)variant when the distance matches the index configuration.[Indexes]Made full-text, COUNT and HNSW compaction SSI-safe so concurrent index activity no longer drops work that was committed after the compaction snapshot. Compaction is now split-phase and generation-guarded, with conditional writes that advance an internal generation key and delete only the exact delta keys captured in the read phase. Full-text and COUNT compaction plans are bounded toCOUNT_BATCH_SIZE(50,000) delta keys per batch, so a long-stalled delta backlog is processed in fixed-size batches instead of being held entirely in memory. HNSW pending updates now use record-keyed entries so sequential writes from multiple SurrealDB nodes against the same remote backend no longer depend on local append-key ordering.[Indexes]Fixed concurrent index conflicts and reduced overhead during full-text concurrent builds. The initial-build write transaction no longer reads!ip/!igqueue handoff records (those are now read through a separate read-only transaction, so the initial-build write transaction stays out of active queue contention).maybe_consumeno longer writes!ipkeys after the initial build phase. The append-phase commit retries on transient transaction conflicts instead of treating them as fatal. Full-text concurrent builds now reuseFullTextIndexacross documents in a batch instead of recreating it per document.[Indexes]Fixed cascade-delete index bugs that caused COUNT-index drift, ghost UNIQUE-index entries on the relation table after deleting a vertex with edges, and phantom UNIQUE-index entries whenIN/OUTrecords were deleted before the relation. Also fixedprocess_lookuphardcodingNonefor the version parameter, which causedSELECT … WITH VERSIONqueries that traverse graph edges to silently return current-state records instead of the requested historical snapshot.[Live Queries]Terminate live queries on session invalidation and TTL expiry.invalidate()now callscleanup_lqs(), mirroringreset(), so all live-query registrations for the session are removed from the notification engine. The dispatcher also compares the session expiry timestamp against the current time before sending each notification and silently skips expired ones, closing a class of bug where revoked sessions continued receiving CREATE / UPDATE / DELETE events indefinitely.[Live Queries]Prevent ignorable evaluation errors in aLIVE SELECT'sWHEREclause orSELECTprojection (type mismatches, invalid function arguments) from propagating back throughtry_join_alland aborting the enclosing write transaction. Such errors now silently skip the notification.[Storage]Fixed long server non-responsiveness when a client cancelssurreal exportmid-stream. The HTTP export handler now exits the proxy task when the body stream channel fails (indicating client disconnect), which causes the next channel send to fail and aborts the export task - releasing the read transaction promptly. On large databases this previously blocked endpoints like/healthwhile the cancelled export ran to completion.[Indexes/SurrealKV]Allow large transactions to insert without flushing mid-transaction by raising the maximum memtable size for SurrealKV.[RPC/WebSocket]Respond with a failure when an in-flight WebSocket request is cancelled rather than silently dropping it, so clients always observe an explicit response.[Transactions]Surface unifiedCannot COMMIT: …errors when an explicitCOMMITafter a previous statement abort fails (e.g. unique constraint), instead of returning only the first statement error.txn.commit()failures and the post-abortCOMMITfast-forward now share the same error wording so clients no longer treat a missing row as success.[Auth]Fixedidresolving toNONEinside a table'sFOR select WHERE … permissionclause when a record was created and selected in the same statement (SELECT * FROM ONLY (CREATE ONLY a)), which previously caused the record to be filtered out by its ownSELECTpermission.RecordId::select_documentnow re-injects theidinto the returned object when it's missing on a transaction-cache hit.[Auth]Fixed user-defined functionPERMISSIONSclauses being evaluated for system-level users (root, namespace, database).PERMISSIONSare intended only for record-level users; system-level users withAction::Viewnow correctly bypass the check and can invoke functions defined withPERMISSIONS NONE. Also removed a hardcodedfn::prefix from theFunctionPermissionserror message that produced double-prefixed names likefn::fn::my_function.[Functions]Addedencoding::json::encodeandencoding::json::decodefor serialising SurrealDB values to / from JSON strings, and added support for UTF-16 surrogate-pair escape sequences (\uD800–\uDFFF) in the JSON parser. The surrogate-pair support is gated on ajson_string_escapesparser setting so SurrealQL string parsing is unaffected.[Functions]Useserde_jsonto parse external JSON inhttp::*functions so valid JSON responses containing escaped forward slashes (\/) - produced by defaultjson_encode()in PHP and other systems - are no longer rejected by SurrealQL's own JSON parser.[Functions]Fixedinfinityoutput and added a non-abbreviated constant path so that the constant is preserved on round-trip.[Functions]Fixed user-duration structure so that durations defined and stored on user definitions round-trip correctly.[Types/SDK]Fixed stack overflow on recursiveSurrealValuetypes so deeply nested or self-referential type definitions no longer crash on encode / decode.[Types/SDK]FixedSurrealValuederive macro emittingr#typeinstead oftypefor raw-identifier struct fields. Fields liker#type: Stringnow serialise as"type"without needing an explicit#[surreal(rename = "type")]attribute.[Types/SDK]Fixed a copy-paste bug inprepare_array(initerator.rs) that checkedx[0]forPart::Lookupafter already matching it asPart::Start, making the lookup-optimisation branch dead code. All edge traversals inside arrayFROMclauses (SELECT * FROM [a:1->edge->table]) previously fell through to the slowerprepare_computedpath; the optimisedprepare_lookuppath is now reached.[Types/SDK]Fixed JSON-patchremoveoperation on lists.[Types/SDK]FixedCREATEfailing for fields typed asset<…>.[Imports/Exports]Fixed re-importing an exported database failing with "field already exists" errors. Export now emitsDEFINE FIELD OVERWRITEso a clean re-import succeeds without manual intervention.[Logging]Fixed JSON log format being unused even when configured.[Logging]Fixed ANSI escape codes leaking into JSON log output (which previously produced corrupt records like"level": "\x1b[32mINFO\x1b[0m") and added per-stream TTY detection so mixed-redirect scenarios likesurreal start > app.logno longer strip ANSI from the stream that is still an interactive terminal.[CLI]Prevent panic on systems with restricted/procaccess (e.g. NixOS withProcSubset=pidhardening) wheresysinfocannot read/proc/meminfoandSystem::total_memory()returns 0. The previouscgroup_limits()assertion put the systemd service into an infinite restart loop on both RocksDB and SurrealKV backends.[Events]Fixed events defined on a vertex table incorrectly firing for related edge-table records during cascade deletion. When deleting a vertex with graph edges (viaRELATE), the edge deletes now run with the edge table's document context so the vertex table's events, views, live queries, changefeeds, and field validation are no longer applied to edge-record mutations.
Newer patch available
Upgrade to 3.1.4
You are viewing the 3.1.0-beta.1 changelog. A newer patch in this release line is available - we recommend running 3.1.4 for the latest fixes and improvements.
Our newsletter
Get tutorials, AI agent recipes, webinars, and early product updates in your inbox every two weeks
No newer release line.
3.0
Updated Mar 27, 2026