Reference
libSQL Rust Reference
The libSQL Rust crate contains everything you need to work with Turso and works flawlessly with popular async runtimes like tokio
.
Installing
Install the crate in your project using the following command:
cargo add libsql
Conditional compilation
The libsql rust client supports conditionally compiling certain features to reduce compile times depending on what features you would like to use.
The following features are available:
Feature | Description |
---|---|
remote | Enables the HTTP-only client, allowing communication with a remote sqld server using pure Rust. Does not require compiling C code for SQLite. Suitable for projects that only need to interact with a remote database. |
core | Enables the local database only, incorporating the C SQLite3 code into the build. This is the foundation for local database operations but does not include additional features like replication or encryption. |
replication | Combines core with additional code required for replication, enabling the embedded replica features. |
encryption | Enables encryption at rest support, adding the necessary code to compile encryption capabilities and expose functions for configuring it. This is optional and not enabled by default, catering to projects that require enhanced data security. |
[dependencies]
libsql = { version = "...", features = ["encryption"] }
Using core
and replication
features require a c compiler. The encryption
feature requires cmake
to be installed on your system.
Initializing
Make sure add the crate to your project at the top of your file:
use libsql::Builder;
let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();
let mut db = Builder::new_remote_replica("local.db", &url, &token).build().await.unwrap();
let conn = db.connect().unwrap();
In-Memory Databases
libSQL supports connecting to in-memory databases for cases where you don’t require persistence:
use libsql::Builder;
let db = Builder::new_local(":memory:").build().await.unwrap();
let conn = db.connect().unwrap();
Local Development
You can work locally using an SQLite file using new_local
:
use libsql::Builder;
let mut db = Builder::new_local("local.db").build().await.unwrap();
let conn = db.connect().unwrap();
Embedded Replicas
You can work with embedded replicas using new_remote_replica
that can sync from the remote URL and delegate writes to the remote primary database:
use libsql::Builder;
let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();
let mut db = Builder::new_remote_replica("local.db", &url, &token).build().await.unwrap();
let conn = db.connect().unwrap();
Manual Sync
The sync
function allows you to sync manually the local database with the remote counterpart:
use libsql::Builder;
let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();
let mut db = Builder::new_remote_replica("local.db", &url, &token).build().await.unwrap();
let conn = db.connect().unwrap();
db.sync().await.unwrap(); // Call sync manually to update local database
If you require full control over how frames get from your instance of sqld
(libSQL Server), you can do this using new_local_replica
and sync_frames
. Reach out to us on Discord if you want to learn more.
Periodic Sync
The periodic_sync
function allows you to set an interval for automatic synchronization of the database in the background:
use libsql::Builder;
use std::time::Duration;
let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();
let mut db = Builder::new_remote_replica("local.db", &url, &token)
.periodic_sync(Duration::from_secs(300)) // Sync every 5 minutes
.build()
.await.unwrap();
let conn = db.connect().unwrap();
Read Your Own Writes
The read_your_writes
function configures the database connection to ensure that writes are immediately visible to subsequent read operations initiated by the same connection. This is enabled by default, and is particularly important in distributed systems to ensure consistency from the perspective of the writing process.
You can disable this behavior by passing false
to the function:
use libsql::Builder;
let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();
let mut db = Builder::new_remote_replica("local.db", &url, &token)
.read_your_writes(false) // Disable reading your own writes
.build()
.await.unwrap();
let conn = db.connect().unwrap();
Encryption
To enable encryption on a SQLite file (new_local
or new_remote_replica
), make sure you have the encryption
feature enabled, and pass the encryption_config
:
use libsql::Builder;
use bytes::Bytes;
let url = env::var("LIBSQL_URL").expect("LIBSQL_URL must be set");
let token = env::var("LIBSQL_AUTH_TOKEN").unwrap_or_default();
let cipher = Cipher::YourChosenCipher;
let encryption_key_bytes = Bytes::from("your_secure_encryption_key_here");
let encryption_config = EncryptionConfig {
cipher,
encryption_key: encryption_key_bytes,
};
let mut db = Builder::new_remote_replica("local.db", &url, &token)
.encryption_config(encryption_config) // Apply encryption configuration
.build()
.await
.unwrap();
let conn = db.connect().unwrap();
Encrypted databases appear as raw data and cannot be read as standard SQLite databases. You must use the libSQL client for any operations — learn more.
Simple query
You can pass a string to execute()
to invoke a SQL statement, as well as optional arguments:
Prepared Statements
You can prepare a cached statement using prepare()
and then execute it with query()
:
let stmt = db_conn.prepare("SELECT * FROM users").await?;
db_conn.query(&stmt, [&1]).await?;
Placeholders
libSQL supports the use of positional and named placeholders within SQL statements:
Deserialization
You can use the de::from_row
function to deserialize a row into a struct:
use libsql::{de, Builder};
let mut stmt = conn
.prepare("SELECT * FROM users WHERE id = ?1")
.await
.unwrap();
let row = stmt
.query([1])
.await
.unwrap()
.next()
.await
.unwrap()
.unwrap();
#[derive(Debug, serde::Deserialize)]
struct User {
name: String,
age: i64,
vision: f64,
avatar: Vec<u8>,
}
let user = de::from_row::<User>(&row).unwrap();
Batch Transactions
A batch consists of multiple SQL statements executed sequentially within an implicit transaction. The backend handles the transaction: success commits all changes, while any failure results in a full rollback with no modifications.
conn.execute_batch(r#"
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
INSERT INTO users (name) VALUES ('Alice');
INSERT INTO users (name) VALUES ('Bob');
"#).await?;
Interactive Transactions
Interactive transactions in SQLite ensure the consistency of a series of read and write operations within a transaction’s scope. These transactions give you control over when to commit or roll back changes, isolating them from other client activity.
transaction()
— with default transaction behavior (DEFFERED
)transaction_with_behavior()
— with custom transaction behavior
Was this page helpful?