Changelog¶
All notable changes to this project will be documented in this file.
The format follows Keep a Changelog and this project adheres to Semantic Versioning.
[Unreleased]¶
[0.29.0] - 2026-06-17¶
Added¶
- Full-text search (BM25) is now first-class — the sparse leg of hybrid retrieval. Define a
DEFINE ANALYZERin code withanalyzer(name)/standard_analyzer(name)(AnalyzerDefinition+Tokenizer+TokenFilter, rendered viagenerate_analyzer_sql/generate_analyzer_sql_with_options); build a BM25-scored full-text index withbm25_index(name, columns, analyzer)(orsearch_index(...).with_analyzer(...).with_bm25().with_highlights()); and run the lexical query withQuery::fulltext_search(field, reference, query)+Query::search_score(reference, alias), or thefulltext_search_query(...)helper. Pair it withvector_searchand fuse the two result orders by rank (Reciprocal Rank Fusion). Verified end-to-end against an embedded SurrealDB engine intests/integration_fulltext.rs.
Fixed¶
- Full-text index now emits the SurrealDB 3.x
FULLTEXTkeyword. The full- text index keyword was renamed fromSEARCHtoFULLTEXTin SurrealDB 3.0, so the previous output (... SEARCH ANALYZER ascii) was a parse error on v3.IndexType::Search/search_index/IndexDefinition::to_surql*and the migration diff now emitFULLTEXT, and theINFO FOR TABLEindex parser recognises both spellings. Seedocs/v3-patterns.md§9 — including the note that the v3 streaming executor's full-text scan returns rows in BM25 relevance order butsearch::scoreis not plumbed through it (returns 0), so rank by the scan's natural order.
[0.28.1] - 2026-06-12¶
Fixed¶
- Connect retries no longer mask the real failure behind "Already connected". The SDK engine connects once per handle and rejects a second
connect; after a partially-successful attempt (engine up, then credential signin or namespace selection failed), every retry died on that rejection, so the surfaced error wasAlready connectedinstead of the actual failure (e.g.There was a problem with authentication), and retries 2..n never re-attempted the failing step at all.DatabaseClientnow tracks engine-level connection state: a retry — or aconnecton an already-connected client (reconnect), which failed the same way — skips the engine connect and resumes at the step that failed.
[0.28.0] - 2026-06-06¶
Fixed¶
- Table-level
PERMISSIONSnow render correctly.TableDefinitionemitted a malformedDEFINE FIELD PERMISSIONS FOR {action} ON TABLE ...per action, which SurrealDB rejects (Unexpected token FOR). Table permissions now render inline on theDEFINE TABLEstatement (... PERMISSIONS FOR select WHERE ... FOR create WHERE ...), the only valid placement. Affectsto_surql_with_options/to_surql_all_with_optionsandgenerate_table_sql. - Edge table
PERMISSIONSwere silently dropped.EdgeDefinitionignored itspermissionsentirely; they now render inline on theDEFINE TABLE ... TYPE RELATIONstatement. - Migration diff renders a permissions change as valid SurrealQL. A
ModifyPermissionsdiff emitted the same malformedDEFINE FIELD PERMISSIONSform; it now emits a singleDEFINE TABLE <t> PERMISSIONS ...statement. (ASCHEMAFULLtable re-defined this way falls back toSCHEMALESS; full-mode fidelity is a follow-up once the diff carries the table mode.)
Added¶
- Expression-valued
UPDATE ... SETfor atomic read-modify-writes.Query::update_setbegins anUPDATE <target> SET ...whose assignments are supplied viaset(literal) orset_expr(expression-valued), combinable withwhere_andRETURN.Expressionnow implements the standard arithmetic operators (+ - * /over anythingInto<Expression>, withFrom<i64|i32|f64>for numeric literals), so aSETvalue can reference the row's current fields — e.g.UPDATE t SET n = n + 1 WHERE ...collapses a read-modify-write into one statement. is_none/is_not_noneoperators (field IS NONE) — the correct guard for an absent optional field, which SurrealDB reports asNONE, notNULL.AccessDefinition::to_surql_with_options(if_not_exists)andgenerate_access_sql_with_options(access, if_not_exists)to emitDEFINE ACCESS IF NOT EXISTS ...for idempotent re-application (e.g. a persistent store applying its schema on every connect).
[0.2.7] - 2026-05-30¶
Added¶
- Typed
record<table>field emission.FieldDefinitiongains atarget_tablefield, andrecord_field(name, Some("user")), the newtarget_table(...)builder setter, andwith_target_table(...)all renderTYPE record<user>. A canonicaltype::record("X", $value)coercion on a RECORD field is auto-lifted intotarget_tableat build time, dropping the now redundant VALUE clause. TheDEFINE FIELDparser readsrecord<table>back intotarget_tableso typed records round-trip.
[0.2.6] - 2026-05-22¶
Maintenance release focused on closing open security, dependency, and CI-hygiene work. No public-API breaking changes.
Security¶
- Bumped
opensslfrom0.10.79to0.10.80viacargo update, closing CVE-2026-45784 (medium severity, potential out-of-bounds write inCipherCtxRef::cipher_update_inplacefor AES-KW-PAD ciphers). The crate's defaultclient-rustlsbackend never linksopenssl; the bump only affects consumers that opt into theclient/client-tlsfeature.
Fixed¶
- Daily Security Audit workflow no longer fails on every scheduled run. Replaced the deprecated Node.js 20
rustsec/audit-check@v2.0.0action with a directtaiki-e/install-action+cargo auditinvocation. The new step exits non-zero only on actual vulnerabilities; informational unmaintained warnings (atomic-polyfill, bincode 2.x) are surfaced as logs because they reach the dep graph transitively throughsurrealdband are not actionable from this repo.
Changed¶
- CI workflow (
ci.yml) now runs thestableRust toolchain only on push and pull-request triggers.betatoolchain coverage moved to the daily Nightly workflow so regressions still surface within 24 hours without paying for two parallel jobs on every PR rev. - Added
paths-ignorefilters toci.ymlandcoverage.ymlso pure documentation, LICENSE, or.editorconfig/.gitignorechanges no longer trigger a full compile + clippy + test run.docs.ymlalready handles documentation rebuilds. - Dependabot auto-merge workflow now uses
dependabot/fetch-metadata@v3andlewagon/wait-on-check-action@v1.7.0, the latest stable majors of both actions. docs/features.mdanddocs/migration.mdcorrected: the default feature has beenclient-rustlssince0.2.3, notclient.
Added¶
docs/connection-management.mddocuments the task-scoped current client,ConnectionRegistry,AuthManager,StreamingManager/LiveQuery, andTransaction.docs/caching.mddocumentsCacheManager, theMemoryCacheandRedisCachebackends, thecached/cached_with/cache_key_forhelpers, and the invalidation surface.docs/orchestration.mddocumentsEnvironmentConfig/EnvironmentRegistry,DeploymentPlan,DeploymentCoordinator, the four built-inDeploymentStrategyimplementations (Sequential, Parallel, Rolling, Canary),DeploymentResult, andcheck_environment_health/verify_connectivity.docs/migration.mdnow carries anUpgrading 0.2.5 -> 0.2.6section.mkdocs.ymlnavigation surfaces the three new module pages under Guides.
[0.2.5] - 2026-05-19¶
Brings the parser, RecordID, and batch surfaces to feature parity with the surql-py 1.6.4 / 1.7.0 release window (and the sibling surql v1.5.0 TypeScript port). Also hardens the CI workflow set so PRs do not double- run and the docs build no longer serialises every ref behind a single queue.
Added¶
-
parse_edge_info(edge_name, info, define_table)insurql::schema::parser— counterpart to [parse_table_info] for graph-edge tables defined viaedge_schema/ [EdgeDefinition]. Edge mode is detected from theDEFINE TABLEstatement:TYPE RELATIONresolves toEdgeMode::Relation,SCHEMAFULLtoEdgeMode::Schemafull, anything else toEdgeMode::Schemaless.FROM <table>andTO <table>are extracted independently so a malformed live definition that lost one clause surfaces as missing-endpoint drift instead of a parse failure. OnRelation-mode edges the auto-emittedinandoutfield declarations SurrealDB stores are stripped on parse — they are implicit whenTYPE RELATIONis set, so the code-sideEdgeDefinitiondoes not declare them and round-trip diffs were flagging them as orphan additions. Per-actionPERMISSIONSround-trip via the newparse_table_permissionshelper. -
parse_table_permissions(definition)insurql::schema::parser— extracts the per-actionPERMISSIONSrules from aDEFINE TABLEstatement string. ReturnsNonefor the trivialNONE/FULLpostures (the code-side helpers have no representation for those) and for definitions without aPERMISSIONSclause. Recognises the expanded form (FOR select WHERE r1 FOR create WHERE r2 …), the comma-joined form v3 emits when several actions share a rule (FOR select, create, update, delete WHERE r), and arbitrary mixes of both. The Rustregexcrate does not support lookahead, so the body is split onFORboundaries before applying the per-clause matcher — same per-action map shape the surql-py port produces, no lookahead. -
parse_table_info(name, info, define_table)— the optional third argument is theDEFINE TABLE <name> ...statement string, fetched fromINFO FOR DB'stables.<name>entry. SurrealDB v3'sINFO FOR TABLEdoes not include the table-levelDEFINE TABLEstatement, so table mode andPERMISSIONScannot be recovered from it alone. Withoutdefine_tablethe parser falls back to the legacytbkey inside the response (the v1 / v2 shape) and table mode defaults toSchemalesson v3. -
strip_brackets(value)insurql::types, re-exported from the crate root. SurrealDB v3 wraps record-id keys that contain anything other than[A-Za-z_][A-Za-z0-9_]*or pure digits in unicode angle brackets⟨ … ⟩(U+27E8 / U+27E9). Downstream consumers that wanted the baretable:idshape were callingvalue.replace('⟨', "").replace('⟩', "")themselves at every API boundary;strip_bracketscentralises that strip and also accepts the legacy ASCII< … >form.Noneis passed through untouched so the helper is safe to apply unconditionally. -
upsert_many_in_tx(txn, table, items, conflict_fields)— atomic counterpart to [upsert_many]. Queues oneUPSERT <target> CONTENT { … }statement per item on the supplied [Transaction] buffer; the per-record statements inherit the surroundingBEGIN TRANSACTION/COMMIT TRANSACTIONframing so a single bad record rolls back the entire batch on commit instead of leaving the database half-seeded.Transaction::executequeues raw SQL without param bindings, so the CONTENT payload is rendered as a SurrealQL object literal (rather than$data-bound as it is in autocommit mode). Bothupsert_manyandupsert_many_in_txaccept an optionalconflict_fieldsslice that emits an inline-valueWHERE … AND …clause appended to each UPSERT.
Fixed¶
-
build_upsert_queryemittedUPSERT INTO <table> [ {…}, {…} ], which SurrealDB v3 rejects with a parse error — v3 wants a single record-id or table target afterUPSERT, not an array literal. The renderer now emits oneUPSERT <target> CONTENT { … }statement per item, joined by;, matching the surql-py 1.7.0 / surql 1.5.0 shape that is portable across the sibling ports. The pre-0.2.5 source comment acknowledged the bug ("not valid SurrealDB v3 SurrealQL") but kept the broken shape for byte-for-byte parity with the older surql-py renderer; that parity bridge is no longer needed. -
build_upsert_queryconflict_fieldsemittedWHERE field = $item.field, which has no$itembinding in scope at the call site (and the rendered string is also fed verbatim toTransaction::execute, which queues raw SQL without binding params). The renderer now inlines the conflict values (WHERE email = 'a@b.com' AND tenant = 'BFS'), matching the surql 1.5.0 fix. -
RecordID::Displayemitted ASCII<id>brackets for ids that could not be rendered bare. SurrealDB v3 rejects ASCII</>in record-id positions withUnexpected token '<', expected a record-id key; the output now uses the v3-correct unicode escape syntax⟨id⟩(U+27E8 / U+27E9).RecordID::parseaccepts both forms on input so legacy wire payloads still round-trip cleanly. Breaking for callers that asserted on the exactDisplayoutput; the SQL shape is identical otherwise. -
RecordID::needs_angle_bracketsaccepted leading-digit ids bare (chunk:1abc). The pre-0.2.5simple_id_patternwas[A-Za-z0-9_]+, which let1abcslip through and produced a literal v3 rejects withUnexpected token. The newidentifier_id_patternis[A-Za-z_][A-Za-z0-9_]*, with a separate allow-list for pure- digit strings (which v3 parses as integer-key ids and round-trips bare). Matches surql-py 1.7.0.
Changed¶
-
upsert_manyno longer routes throughUPSERT <table> CONTENT $datafor items that lack anidfield. The autocommit path always pins the target —data.idwhen present,<table>otherwise — and stripsidfrom the bound payload so v3 does not reject the duplicate field. -
CI workflow set hardened against runaway runs:
docs.ymlswitched from the globalgroup: pagesconcurrency queue (which serialised every build + deploy across all refs and caused multi-day stalls when a long-running deploy held the queue) to a per-ref group withcancel-in-progress: true.ci.ymlandcoverage.ymlgained per-ref concurrency groups so rapid pushes to a PR cancel the in-progress run. The redundantpush: branches: ['release/**']triggers were dropped — release branches only ever receive PRs that already fired the workflow viapull_request, so the push trigger was pure duplicated work.audit.yml,dep-review.yml, andpr-title.ymlgained per-ref concurrency groups so a sequence of PR edits cancels the in-progress lint and only the latest revision is checked.
Verified¶
cargo fmt --all -- --check— clean.cargo clippy --lib --all-features --tests -- -D warnings— clean.cargo test --lib --no-default-features— 927 passed, 0 failed.cargo test --lib --all-features— 1088 passed, 0 failed (baseline was 1066 on 0.2.4; +22 regression tests coveringparse_edge_info,parse_table_permissions,strip_brackets, unicode-bracketRecordID::Display, and the per-recordbuild_upsert_queryshape).- All integration tests compile.
[0.2.4] - 2026-05-02¶
Added¶
client-wasmfeature (Oneiriq/surql-rs#115). Wasm-friendly client surface that compiles cleanly towasm32-unknown-unknown. Pullssurrealdbwithprotocol-ws+kv-memonly -- norustls/native-tls/reqwest, since browsers terminate TLS at the WebSocket layer andkv-memlets wasm callers run an embedded engine for local state and tests. Exposes the sameDatabaseClient/executor/crud/graph/batchAPI asclient-rustls.[target.'cfg(target_arch = "wasm32")'.dependencies]block inCargo.tomlthat overridestokioto the wasm-buildable subset (sync,macros,rt,time) and pullsgetrandom 0.3with thewasm_jsfeature soulid/rand_corelink on wasm..cargo/config.tomlwith the--cfg=getrandom_backend="wasm_js"rustflag required bygetrandom 0.3onwasm32-unknown-unknown(the feature flag alone is insufficient -- see https://docs.rs/getrandom/0.3/#webassembly-support).scripts/check-wasm.sh-- canonical local + CI gate for the wasm build. On macOS auto-detects Homebrew LLVM socc-rscan handring 0.17's build script a wasm-capable clang (Apple's/usr/bin/clanghas no wasm32 backend).
Changed¶
- The optional
tokiodependency moved from a top-level[dependencies]declaration withfeatures = ["full"]to two target-specific declarations: native targets keep the historical["full"]feature set, whilewasm32-*targets get["sync", "macros", "rt", "time"]. No source-level API changes.
Fixed¶
cargo build --target wasm32-unknown-unknown -p oneiriq-surql --no-default-features --features client-wasmnow succeeds on a system with a wasm-capable clang in scope. Unblocks Oneiriq/pixel-stroke#236 (web-build ofpixel-stroke-persistence).
[0.2.3] - 2026-05-02¶
Changed¶
- The default feature set is now
["client-rustls"](pure-Rust TLS). Previously the default was["client"], which pulledsurrealdb/native-tlsandreqwest/default-tlsand thereforeopenssl-sysinto the dependency graph. The historical native-tls backend is still available via theclientfeature (now also exposed under theclient-tlsalias) for consumers that need the system OpenSSL stack. - The
cliandorchestrationfeatures now depend onclient-rustlsinstead ofclientso thatcargo install oneiriq-surql --features cliand other typical builds no longer compile againstopenssl-sys.
Security¶
- Drops the
openssl-systransitive dependency from the default dependency graph, clearing the following Dependabot advisories on this crate's published default build: - rust-openssl: incorrect bounds assertion in AES key wrap (HIGH)
- rust-openssl: unchecked callback length in PSK / cookie trampolines leaks adjacent memory to peer (HIGH)
- rust-openssl:
MdCtxRef::digest_final()writes past caller buffer with no length check (HIGH) - Consumers who explicitly opt into
--features client(or theclient-tlsalias) still link the system OpenSSL stack and remain subject to upstreamrust-openssladvisories.
[0.2.2] - 2026-04-21¶
Added¶
client-rustlsfeature (Oneiriq/surql-rs#97). Same surface as the defaultclientfeature, but with a pure-Rust TLS stack (rustls+webpki-roots) instead ofnative-tls. Enables building on runners that do not havelibssl-dev/ the system OpenSSL headers installed. See docs/features.md for the trade-offs and docs/migration.md for a switching guide.
Changed¶
- The
clientfeature now explicitly selectssurrealdb/native-tlsandreqwest/default-tls. Behaviour is unchanged for existing consumers (the implicit TLS stack was alreadynative-tls), but the TLS backend is no longer inherited from upstream defaults -- it is pinned by the feature flag. No API changes. - Optional
surrealdbandreqwestdependencies are declared withdefault-features = falseso the TLS backend is selected exclusively byclient/client-rustls.
[0.2.1] - 2026-04-18¶
Documentation¶
docs/features.md-- full feature-flag reference.docs/query-ux.md-- before / after walkthroughs for the 0.2 crate-root helpers (type_record,type_thing,extract_many,has_result,select_expr,execute,aggregate_records).docs/v3-patterns.md-- SurrealDB v3-specific SurrealQL shapes (subprotocol handshake,type::recordrename, datetime coercion, unrolled graph depth, rejectedUPSERT INTO [...], buffered transactions,SurrealValueavoidance).docs/cli.md-- full subcommand reference (replaces the pre-0.1 "planned" placeholder).docs/migration.md-- 0.1.x -> 0.2.x upgrade notes.- Updated README top-level example with
type_record,Query::select_expr,Query::execute,aggregate_records. - Updated
mkdocs.ymlnav with the new pages anddocs.rs/oneiriq-surqlreference link. - Fixed pre-existing rustdoc intra-doc link warnings so
cargo doc --no-deps --all-featuressucceeds underRUSTDOCFLAGS="-D warnings".
No API changes.
[0.1.0 - 0.2.0] see releases¶
Added¶
migration::versioning--VersionedSnapshot,VersionGraph, andcompare_snapshotsfor DAG-based migration history.migration::generator-- generate migration files (generate_migration,generate_initial_migration,create_blank_migration,generate_migration_from_diffs) with atomic writes and round-trip load.migration::diff-- schema diff engine (diff_tables,diff_fields,diff_indexes,diff_events,diff_permissions,diff_edges,diff_schemas).migration::{models, discovery}--.surqlfile-format migrations with-- @metadata/-- @up/-- @downsection markers and SHA-256 checksum.schema::{visualize, themes, utils}-- Mermaid / GraphViz / ASCII diagrams with modern / dark / forest / minimal themes.schema::parser-- parses SurrealDBINFO FOR DB/INFO FOR TABLEresponses back into schema definitions.schema::{validator, validator_utils}-- cross-schema validation with severity-filtered reports.schema::{sql, registry}-- full DEFINE-statement composition and a thread-safeSchemaRegistry.schema::{fields, table, edge, access}-- code-first schema DSL.query::{builder, helpers}-- immutableQuerywith fluent chaining.query::expressions-- 25+ function builders and typed expression kinds.query::{hints, results}-- query optimization hints + typed result wrappers with raw-response extraction helpers.connection::{config, auth}-- connection configuration (URL / ns / db / timeouts / retry / live-queries gate) + auth credential types.types::{operators, record_id, record_ref, surreal_fn, reserved, coerce}-- operator enum +RecordID<T>with angle-bracket syntax + reserved-word checks + ISO-8601 datetime coercion.error::SurqlError-- unified error enum withContextchaining trait.
Notes¶
This is a pre-release port of surql-py targeting 1:1 feature parity. The runtime async client, CRUD executor, and CLI land in the 0.1 -> 0.2 window.