Version 3 of the surrealdb crate changes how Rust values reach the database and where its types live. This guide lists each change that can break a project written for version 2, with the 2.x code and the code that replaces it. The same changes apply when you move straight to the current 4.x crate, so the examples use names that both versions share. The sections are in order of how much code each change usually affects, so work through them from the top.
The API reference for the 2.x crate stays on docs.rs, for a project that has to stay on version 2 for now.
The crate only connects to a server in the range it supports. Version 2 connects to SurrealDB 1.2.0 up to, but not including, 3.0.0. Version 3 connects to 3.x, and version 4 connects to 3.x and 4.x. So no version of the crate works with both a 2.x server and a 4.x server. Upgrade the server to 3.x first, as the 2.x to 3.x upgrade guide describes, and change the crate at the same time.
What changed
Values that go to and from the database implement
SurrealValue, which you derive, in place of serde'sSerializeandDeserialize.RecordId,Value,Datetimeand the other value types are in thesurrealdb::typesmodule, and thesurrealdb::sqlmodule is gone.RecordId::new(table, key)creates a record ID, in place ofRecordId::from((table, key)).Root,Namespace,DatabaseandRecordhold ownedStrings in place of&str.query()returns anIndexedResults, which was calledResponse.
1. Update the dependency
Change the version in Cargo.toml, keeping the features the project already uses:
[dependencies]
surrealdb = "2"[dependencies]
surrealdb = "4"Version 4 no longer has the kv-rocksdb, kv-indxdb and kv-tikv features. The 3.x to 4.x upgrade guide lists what replaces each of them.
2. Derive SurrealValue in place of serde
In version 2, a struct passed to .content() or returned by .select() implemented serde's Serialize and Deserialize. In version 3 and later, those methods take and return types that implement SurrealValue, which keeps record IDs, datetimes and the other SurrealDB types intact on the way through. Derive it in place of the serde traits:
use serde::{Deserialize, Serialize};
use surrealdb::RecordId;
#[derive(Debug, Serialize, Deserialize)]
struct Person {
id: RecordId,
name: String,
age: u8,
}use surrealdb::types::{RecordId, SurrealValue};
#[derive(Debug, SurrealValue)]
struct Person {
id: RecordId,
name: String,
age: u8,
}A struct that also goes to or comes from JSON, such as the body of a request in a web framework, can derive both: #[derive(SurrealValue, Serialize, Deserialize)]. The derive takes attributes of its own for renaming fields and choosing how enums are stored, which SurrealValue attributes lists. To work without a struct, flexible typing shows Value and serde_json::Value.
3. Import value types from surrealdb::types
The value types moved from the crate root into the surrealdb::types module. Version 2 also re-exported the server's own types under surrealdb::sql, and that module no longer exists, so a type imported from it comes from surrealdb::types as well:
use surrealdb::sql::Datetime;
use surrealdb::{RecordId, RecordIdKey, Value};use surrealdb::types::{Datetime, RecordId, RecordIdKey, Value};Surreal, the engine and opt modules, Notification and Result keep their paths.
4. Create record IDs with RecordId::new
Version 2 converted a tuple into a RecordId. Version 3 and later take the table and the key as two arguments:
let id = RecordId::from(("person", "john"));let id = RecordId::new("person", "john");The methods that take a resource still accept a tuple, so db.select(("person", "john")) works in both versions.
5. Pass owned strings to the credentials
Root, Namespace, Database and Record held borrowed &str fields in version 2. They hold Strings in version 3 and later:
db.signin(Root {
username: "root",
password: "secret",
})
.await?;db.signin(Root {
username: "root".to_string(),
password: "secret".to_string(),
})
.await?;The params of Record implement SurrealValue in place of Serialize, as in section 2.
6. Rename Response to IndexedResults
The type that query() returns is IndexedResults. Its .take() and .check() methods work as before, so only code that names the type changes:
use surrealdb::Response;
fn first_names(mut response: Response) -> surrealdb::Result<Vec<String>> {
response.take("name")
}use surrealdb::IndexedResults;
fn first_names(mut response: IndexedResults) -> surrealdb::Result<Vec<String>> {
response.take("name")
}