Rust SQLite FFI绑定库libsql-ffi的使用,实现高效安全的嵌入式数据库操作与SQLite交互

Rust SQLite FFI绑定库libsql-ffi的使用,实现高效安全的嵌入式数据库操作与SQLite交互

安装

在项目目录中运行以下Cargo命令:

cargo add libsql-ffi

或者在Cargo.toml中添加以下行:

libsql-ffi = "0.9.19"

完整示例代码

以下是一个使用libsql-ffi进行SQLite数据库操作的完整示例:

use libsql_ffi::{Database, Statement};
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 打开或创建数据库
    let db_path = Path::new("example.db");
    let db = Database::open(db_path)?;
    
    // 创建表
    let create_table_sql = "CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL
    )";
    db.execute(create_table_sql, ())?;
    
    // 插入数据
    let insert_sql = "INSERT INTO users (name, email) VALUES (?, ?)";
    let mut stmt = db.prepare(insert_sql)?;
    stmt.bind(1, "Alice")?;
    stmt.bind(2, "alice@example.com")?;
    stmt.execute()?;
    
    // 查询数据
    let select_sql = "SELECT id, name, email FROM users";
    let mut stmt = db.prepare(select_sql)?;
    
    while stmt.step()? {
        let id: i64 = stmt.column_int(0)?;
        let name: String = stmt.column_text(1)?;
        let email: String = stmt.column_text(2)?;
        
        println!("User: id={}, name={}, email={}", id, name, email);
    }
    
    // 更新数据
    let update_sql = "UPDATE users SET email = ? WHERE name = ?";
    let mut stmt = db.prepare(update_sql)?;
    stmt.bind(1, "new.alice@example.com")?;
    stmt.bind(2, "Alice")?;
    stmt.execute()?;
    
    // 删除数据
    let delete_sql = "DELETE FROM users WHERE name = ?";
    let mut stmt = db.prepare(delete_sql)?;
    stmt.bind(1, "Alice")?;
    stmt.execute()?;
    
    Ok(())
}

主要特性

  1. 安全绑定:通过FFI安全地与SQLite C API交互
  2. 线程安全:正确处理SQLite的线程安全模式
  3. 事务支持:提供BEGIN/COMMIT/ROLLBACK操作
  4. 预编译语句:支持参数化查询,防止SQL注入
  5. 错误处理:完善的错误处理机制

文档

更多详细用法请参考官方文档。该库遵循MIT许可证。

所有者

  • tursodatabase/publish团队
  • Glauber Costa
  • Pekka Enberg

1 回复

Rust SQLite FFI绑定库libsql-ffi的使用指南

简介

libsql-ffi是一个Rust语言的SQLite FFI绑定库,它提供了高效、安全的嵌入式数据库操作能力,允许Rust程序与SQLite数据库进行交互。这个库特别适合需要在Rust项目中嵌入轻量级数据库的场景。

主要特性

  • 完全兼容SQLite3的C API
  • 提供内存安全的Rust接口
  • 支持事务操作
  • 线程安全设计
  • 支持预编译语句
  • 错误处理完善

安装方法

在Cargo.toml中添加依赖:

[dependencies]
libsql-ffi = "0.1"  # 请使用最新版本

基本使用方法

1. 打开和关闭数据库

use libsql_ffi::{Database, OpenFlags};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 打开数据库(如果不存在则创建)
    let db = Database::open(":memory:", OpenFlags::SQLITE_OPEN_READWRITE | OpenFlags::SQLITE_OPEN_CREATE)?;
    
    // 执行SQL语句
    db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)", ())?;
    
    // 数据库会在离开作用域时自动关闭
    Ok(())
}

2. 执行查询和获取结果

use libsql_ffi::{Database, Statement};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Database::open("test.db", OpenFlags::SQLITE_OPEN_READWRITE | OpenFlags::SQLITE_OPEN_CREATE)?;
    
    // 插入数据
    db.execute("INSERT INTO users (name, age) VALUES (?, ?)", ("Alice", 30))?;
    db.execute("INSERT INTO users (name, age) VALUES (?, ?)", ("Bob", 25))?;
    
    // 准备查询语句
    let mut stmt = db.pprepare("SELECT id, name, age FROM users WHERE age > ?")?;
    
    // 绑定参数并执行查询
    let rows = stmt.query(&[&26])?;
    
    // 遍历结果
    for row in rows {
        let row = row?;
        let id: i64 = row.get(0)?;
        let name: String = row.get(1)?;
        let age: i64 = row.get(2)?;
        println!("User {}: {}, {} years old", id, name, age);
    }
    
    Ok(())
}

3. 使用事务

use libsql_ffi::{Database, TransactionBehavior};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Database::open("test.db", OpenFlags::SQLITE_OPEN_READWRITE)?;
    
    // 开始事务
    let tx = db.transaction(TransactionBehavior::Immediate)?;
    
    // 在事务中执行操作
    tx.execute("UPDATE users SET age = age + 1 WHERE name = ?", ("Alice",))?;
    tx.execute("UPDATE users SET age = age + 1 WHERE name = ?", ("Bob",))?;
    
    // 提交事务
    tx.commit()?;
    
    Ok(())
}

高级用法

1. 自定义SQL函数

use libsql_ffi::{Database, FunctionFlags, Value};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Database::open(":memory:", OpenFlags::SQLITE_OPEN_READWRITE)?;
    
    // 注册自定义函数
    db.create_scalar_function(
        "square",
        1,
        FunctionFlags::SQLITE_UTF8,
        |ctx, args| {
            let value = args[0].as_integer()?;
            ctx.result(value * value)
        },
    )?;
    
    // 使用自定义函数
    let result: i64 = db.query_row("SELECT square(5)", (), |row| row.get(0))?;
    println!("5 squared is {}", result);  // 输出: 5 squared is 25
    
    Ok(())
}

2. 使用预编译语句提高性能

use libsql_ffi::{Database, Statement};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db = Database::open("test.db", OpenFlags::SQLITE_OPEN_READWRITE)?;
    
    // 准备预编译语句
    let mut stmt = db.prepare("INSERT INTO users (name, age) VALUES (?, ?)")?;
    
    // 批量插入数据
    let users = [("Charlie", 28), ("David", 35), ("Eve", 22)];
    
    for (name, age) in &users {
        stmt.execute(&[name, age])?;
    }
    
    Ok(())
}

错误处理

libsql-ffi提供了详细的错误信息:

use libsql_ffi::{Database, Error};

fn main() {
    match Database::open("/nonexistent/path.db", OpenFlags::SQLITE_OPEN_READONLY) {
        Ok(_) => println!("Database opened successfully"),
        Err(Error::CannotOpen(_)) => eprintln!("Failed to open database file"),
        Err(e) => eprintln!("Database error: {}", e),
    }
}

性能提示

  1. 对于频繁执行的查询,使用预编译语句(Statement)
  2. 批量操作时使用事务
  3. 考虑使用内存数据库(:memory:)进行临时数据处理
  4. 合理使用索引提高查询性能

完整示例代码

下面是一个完整的示例,展示了如何使用libsql-ffi进行数据库操作:

use libsql_ffi::{Database, OpenFlags, Statement, TransactionBehavior};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1. 打开数据库
    let db = Database::open("demo.db", OpenFlags::SQLITE_OPEN_READWRITE | OpenFlags::SQLITE_OPEN_CREATE)?;
    
    // 2. 创建表
    db.execute(
        "CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL,
            price REAL,
            stock INTEGER
        )",
        (),
    )?;
    
    // 3. 使用事务批量插入数据
    let tx = db.transaction(TransactionBehavior::Immediate)?;
    {
        let mut stmt = tx.prepare("INSERT INTO products (name, price, stock) VALUES (?, ?, ?)")?;
        
        let products = [
            ("Laptop", 999.99, 10),
            ("Phone", 699.99, 15),
            ("Tablet", 399.99, 20),
        ];
        
        for (name, price, stock) in products {
            stmt.execute(&[name, price, stock])?;
        }
    }
    tx.commit()?;
    
    // 4. 查询数据
    let mut stmt = db.prepare("SELECT id, name, price, stock FROM products WHERE stock > ?")?;
    let rows = stmt.query(&[&10])?;
    
    println!("Products with stock > 10:");
    for row in rows {
        let row = row?;
        let id: i64 = row.get(0)?;
        let name: String = row.get(1)?;
        let price: f64 = row.get(2)?;
        let stock: i64 = row.get(3)?;
        
        println!("ID: {}, Name: {}, Price: ${:.2}, Stock: {}", id, name, price, stock);
    }
    
    Ok(())
}

这个完整示例展示了:

  1. 数据库的创建和打开
  2. 表的创建
  3. 使用事务进行批量插入
  4. 使用预编译语句进行查询
  5. 结果集的遍历和处理

libsql-ffi为Rust开发者提供了与SQLite交互的高效安全方式,特别适合嵌入式应用和需要轻量级数据库解决方案的场景。

回到顶部