Rust SQLite绑定库libsql-sys的使用,支持高性能嵌入式数据库操作与SQLite扩展
Rust SQLite绑定库libsql-sys的使用,支持高性能嵌入式数据库操作与SQLite扩展
安装
在项目目录中运行以下Cargo命令:
cargo add libsql-sys
或者在Cargo.toml中添加以下行:
libsql-sys = "0.9.19"
完整示例代码
以下是一个使用libsql-sys进行SQLite数据库操作的完整示例:
use libsql_sys::{Connection, Result};
fn main() -> Result<()> {
// 打开或创建数据库连接
let conn = Connection::open("example.db")?;
// 创建表
conn.execute(
"CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER
)",
[],
)?;
// 插入数据
conn.execute(
"INSERT INTO users (name, age) VALUES (?, ?)",
["Alice", "30"],
)?;
conn.execute(
"INSERT INTO users (name, age) VALUES (?, ?)",
["Bob", "25"],
)?;
// 查询数据
let mut stmt = conn.prepare("SELECT id, name, age FROM users")?;
let rows = stmt.query_map([], |row| {
Ok((
row.get::<i64>(0)?,
row.get::<String>(1)?,
row.get::<Option<i64>>(2)?,
))
})?;
// 打印查询结果
for row in rows {
let (id, name, age) = row?;
println!("id: {}, name: {}, age: {:?}", id, name, age);
}
// 更新数据
conn.execute(
"UPDATE users SET age = ? WHERE name = ?",
[31, "Alice"],
)?;
// 删除数据
conn.execute(
"DELETE FROM users WHERE name = ?",
["Bob"],
)?;
Ok(())
}
特性
- 高性能的SQLite绑定库
- 支持嵌入式数据库操作
- 支持SQLite扩展功能
- 提供安全的Rust接口与SQLite交互
完整示例demo
这是一个更完整的示例,演示了libsql-sys的更多功能:
use libsql_sys::{Connection, Result};
fn main() -> Result<()> {
// 打开内存数据库(临时数据库)
let conn = Connection::open_in_memory()?;
// 启用外键约束
conn.execute("PRAGMA foreign_keys = ON", [])?;
// 创建两个关联表
conn.execute(
"CREATE TABLE departments (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE
)",
[],
)?;
conn.execute(
"CREATE TABLE employees (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
department_id INTEGER,
salary REAL,
FOREIGN KEY(department_id) REFERENCES departments(id)
)",
[],
)?;
// 批量插入数据
let tx = conn.transaction()?;
{
// 插入部门数据
tx.execute("INSERT INTO departments (name) VALUES (?)", ["研发部"])?;
tx.execute("INSERT INTO departments (name) VALUES (?)", ["市场部"])?;
// 插入员工数据
let mut stmt = tx.prepare(
"INSERT INTO employees (name, department_id, salary) VALUES (?, ?, ?)"
)?;
stmt.execute(["张三", "1", "15000.0"])?;
stmt.execute(["李四", "1", "18000.0"])?;
stmt.execute(["王五", "2", "12000.0"])?;
}
tx.commit()?;
// 复杂查询
let mut stmt = conn.prepare(
"SELECT e.name, d.name, e.salary
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE e.salary > ?"
)?;
let rows = stmt.query_map(["13000.0"], |row| {
Ok((
row.get::<String>(0)?, // 员工姓名
row.get::<String>(1)?, // 部门名称
row.get::<f64>(2)? // 薪资
))
})?;
println!("高薪员工列表:");
for row in rows {
let (name, dept, salary) = row?;
println!("{} - {} - {:.2}", name, dept, salary);
}
// 使用预编译语句更新数据
let mut update_stmt = conn.prepare(
"UPDATE employees SET salary = salary * ? WHERE department_id = ?"
)?;
// 研发部加薪10%
update_stmt.execute(["1.1", "1"])?;
Ok(())
}
特性详解
-
高性能的SQLite绑定库 - 提供原生级别的性能,接近直接使用SQLite C API
-
支持嵌入式数据库操作 - 可以创建内存数据库或文件数据库,适合嵌入式应用场景
-
支持SQLite扩展功能 - 支持事务、预编译语句、外键约束等高级特性
-
提供安全的Rust接口 - 使用Rust的错误处理机制,避免内存安全问题
1 回复
Rust SQLite绑定库libsql-sys使用指南
概述
libsql-sys是Rust语言中一个高性能的SQLite绑定库,提供了对SQLite数据库的底层访问能力,支持嵌入式数据库操作和SQLite扩展功能。它是libSQL项目的一部分,libSQL是SQLite的一个分支,专注于提供更好的开发体验和扩展功能。
主要特性
- 提供SQLite3的原始FFI绑定
- 支持编译时链接SQLite或动态加载
- 允许使用SQLite扩展
- 提供对预编译SQLite静态库的支持
- 跨平台支持(Linux, macOS, Windows等)
使用方法
添加依赖
首先在Cargo.toml中添加依赖:
[dependencies]
libsql-sys = "0.1"
基本使用示例
use libsql_sys::ffi;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 打开数据库连接
let mut db: *mut ffi::sqlite3 = std::ptr::null_mut();
let db_path = "test.db";
let flags = ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE;
unsafe {
let result = ffi::sqlite3_open_v2(db_path.as_ptr() as *const i8, &mut db, flags, std::ptr::null());
if result != ffi::SQLITE_OK {
return Err(format!("无法打开数据库: {}", result).into());
}
// 执行SQL语句
let sql = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL);";
let mut err_msg: *mut i8 = std::ptr::null_mut();
let result = ffi::sqlite3_exec(db, sql.as_ptr() as *const i8, None, std::ptr::null_mut(), &mut err_msg);
if result != ffi::SQLITE_OK {
let error = std::ffi::CStr::from_ptr(err_msg).to_string_lossy().into_owned();
ffi::sqlite3_free(err_msg as *mut std::ffi::c_void);
return Err(error.into());
}
// 关闭数据库
ffi::sqlite3_close(db);
}
Ok(())
}
使用预编译SQLite
如果你想使用特定版本的SQLite而不是系统自带的:
[dependencies.libsql-sys]
version = "0.1"
features = ["bundled"]
使用SQLite扩展
use libsql_sys::ffi;
fn load_extension() -> Result<(), Box<dn std::error::Error>> {
let mut db: *mut ffi::sqlite3 = std::ptr::null_mut();
let db_path = ":memory:";
unsafe {
// 打开数据库
let result = ffi::sqlite3_open(db_path.as_ptr() as *const i8, &mut db);
if result != ffi::SQLITE_OK {
return Err("无法打开数据库".into());
}
// 启用扩展加载
let result = ffi::sqlite3_enable_load_extension(db, 1);
if result != ffi::SQLITE_OK {
return Err("无法启用扩展加载".into());
}
// 加载扩展
let ext_path = "./my_extension.so";
let mut err_msg: *mut i8 = std::ptr::null_mut();
let result = ffi::sqlite3_load_extension(
db,
ext_path.as_ptr() as *const i8,
std::ptr::null(),
&mut err_msg
);
if result != ffi::SQLITE_OK {
let error = std::ffi::CStr::from_ptr(err_msg).to_string_lossy().into_owned();
ffi::sqlite3_free(err_msg as *mut std::ffi::c_void);
return Err(error.into());
}
println!("扩展加载成功");
ffi::sqlite3_close(db);
}
Ok(())
}
高级用法
预编译语句示例
use libsql_sys::ffi;
use std::ffi::CString;
fn prepared_statement() -> Result<(), Box<dyn std::error::Error>> {
let mut db: *mut ffi::sqlite3 = std::ptr::null_mut();
let db_path = "test.db";
unsafe {
// 打开数据库
let result = ffi::sqlite3_open(db_path.as_ptr() as *const i8, &mut db);
if result != ffi::SQLITE_OK {
return Err("无法打开数据库".into());
}
// 准备语句
let sql = CString::new("INSERT INTO users (name) VALUES (?)")?;
let mut stmt: *mut ffi::sqlite3_stmt = std::ptr::null_mut();
let result = ffi::sqlite3_prepare_v2(db, sql.as_ptr(), -1, &mut stmt, std::ptr::null_mut());
if result != ffi::SQLITE_OK {
return Err("准备语句失败".into());
}
// 绑定参数
let name = CString::new("Alice")?;
let result = ffi::sqlite3_bind_text(stmt, 1, name.as_ptr(), -1, ffi::SQLITE_TRANSIENT);
if result != ffi::SQLITE_OK {
return Err("绑定参数失败".into());
}
// 执行语句
let result = ffi::sqlite3_step(stmt);
if result != ffi::SQLITE_DONE {
return Err("执行语句失败".into());
}
// 清理
ffi::sqlite3_finalize(stmt);
ffi::sqlite3_close(db);
}
Ok(())
}
注意事项
- libsql-sys提供了原始FFI绑定,使用时需要大量unsafe代码
- 对于更高级的用法,建议考虑基于libsql-sys构建的更高级封装库
- 错误处理需要特别注意,确保所有资源都被正确释放
- 在多线程环境中使用时需要遵守SQLite的线程安全规则
替代方案
如果你需要更安全的、更符合Rust习惯的SQLite接口,可以考虑以下基于libsql-sys的库:
- rusqlite: 提供了更符合Rust习惯的API
- sqlx: 支持异步操作的SQLite接口
libsql-sys更适合需要直接访问SQLite底层功能或需要自定义SQLite构建的场景。
完整示例代码
下面是一个结合了基本操作、预编译语句和错误处理的完整示例:
use libsql_sys::ffi;
use std::ffi::{CString, CStr};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 打开数据库连接
let mut db: *mut ffi::sqlite3 = std::ptr::null_mut();
let db_path = "test.db";
unsafe {
// 打开数据库(读写模式,不存在则创建)
let result = ffi::sqlite3_open_v2(
CString::new(db_path)?.as_ptr(),
&mut db,
ffi::SQLITE_OPEN_READWRITE | ffi::SQLITE_OPEN_CREATE,
std::ptr::null()
);
if result != ffi::SQLITE_OK {
return Err(format!("数据库打开失败,错误码: {}", result).into());
}
// 创建表
let create_sql = CString::new(
"CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
);"
)?;
let mut err_msg: *mut i8 = std::ptr::null_mut();
let result = ffi::sqlite3_exec(
db,
create_sql.as_ptr(),
None,
std::ptr::null_mut(),
&mut err_msg
);
if result != ffi::SQLITE_OK {
let error = CStr::from_ptr(err_msg).to_string_lossy().into_owned();
ffi::sqlite3_free(err_msg as *mut std::ffi::c_void);
return Err(error.into());
}
// 准备插入语句
let insert_sql = CString::new("INSERT INTO users (name, age) VALUES (?, ?)")?;
let mut stmt: *mut ffi::sqlite3_stmt = std::ptr::null_mut();
let result = ffi::sqlite3_prepare_v2(
db,
insert_sql.as_ptr(),
-1,
&mut stmt,
std::ptr::null_mut()
);
if result != ffi::SQLITE_OK {
return Err("准备语句失败".into());
}
// 绑定参数并执行
let name = CString::new("张三")?;
let age = 25;
let result = ffi::sqlite3_bind_text(stmt, 1, name.as_ptr(), -1, ffi::SQLITE_TRANSIENT);
if result != ffi::SQLITE_OK {
ffi::sqlite3_finalize(stmt);
return Err("绑定name参数失败".into());
}
let result = ffi::sqlite3_bind_int(stmt, 2, age);
if result != ffi::SQLITE_OK {
ffi::sqlite3_finalize(stmt);
return Err("绑定age参数失败".into());
}
let result = ffi::sqlite3_step(stmt);
if result != ffi::SQLITE_DONE {
ffi::sqlite3_finalize(stmt);
return Err("执行插入语句失败".into());
}
// 查询数据
let query_sql = CString::new("SELECT id, name, age FROM users")?;
let mut stmt: *mut ffi::sqlite3_stmt = std::ptr::null_mut();
let result = ffi::sqlite3_prepare_v2(
db,
query_sql.as_ptr(),
-1,
&mut stmt,
std::ptr::null_mut()
);
if result != ffi::SQLITE_OK {
return Err("准备查询语句失败".into());
}
println!("查询结果:");
loop {
let result = ffi::sqlite3_step(stmt);
if result == ffi::SQLITE_ROW {
let id = ffi::sqlite3_column_int(stmt, 0);
let name_ptr = ffi::sqlite3_column_text(stmt, 1);
let name = if !name_ptr.is_null() {
CStr::from_ptr(name_ptr as *const i8).to_string_lossy()
} else {
"".into()
};
let age = ffi::sqlite3_column_int(stmt, 2);
println!("ID: {}, 姓名: {}, 年龄: {}", id, name, age);
} else if result == ffi::SQLITE_DONE {
break;
} else {
ffi::sqlite3_finalize(stmt);
return Err("查询执行失败".into());
}
}
// 清理资源
ffi::sqlite3_finalize(stmt);
ffi::sqlite3_close(db);
}
Ok(())
}
这个完整示例展示了:
- 数据库的创建和打开
- 表的创建
- 使用预编译语句插入数据
- 查询数据并处理结果集
- 完善的错误处理
- 资源的正确释放
注意所有操作都在unsafe块中,因为libsql-sys提供了原始FFI绑定。在实际项目中,建议将这些操作封装在安全的抽象层中。