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(())
}

特性

  1. 高性能的SQLite绑定库
  2. 支持嵌入式数据库操作
  3. 支持SQLite扩展功能
  4. 提供安全的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(())
}

特性详解

  1. 高性能的SQLite绑定库 - 提供原生级别的性能,接近直接使用SQLite C API

  2. 支持嵌入式数据库操作 - 可以创建内存数据库或文件数据库,适合嵌入式应用场景

  3. 支持SQLite扩展功能 - 支持事务、预编译语句、外键约束等高级特性

  4. 提供安全的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(())
}

注意事项

  1. libsql-sys提供了原始FFI绑定,使用时需要大量unsafe代码
  2. 对于更高级的用法,建议考虑基于libsql-sys构建的更高级封装库
  3. 错误处理需要特别注意,确保所有资源都被正确释放
  4. 在多线程环境中使用时需要遵守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(())
}

这个完整示例展示了:

  1. 数据库的创建和打开
  2. 表的创建
  3. 使用预编译语句插入数据
  4. 查询数据并处理结果集
  5. 完善的错误处理
  6. 资源的正确释放

注意所有操作都在unsafe块中,因为libsql-sys提供了原始FFI绑定。在实际项目中,建议将这些操作封装在安全的抽象层中。

回到顶部