软软AI中增加sqlite数据库,Tauri中使用sqlite
集成sqlite的需求
作为桌面端,或者单机版软件,数据存储是需要的,集成一个关系型数据库对应一些功能的开发是十分方便的。作为文件型数据库sqlite应用比较广泛,因此将sqlite集成到tauri的项目中。
选取rust中的sqlite第三方库
笔者选取的是rusqlite docs.rs/rusqlite/la…
tauri中的集成方式
在tauri中可以使用command进行扩展、可以使用plugin的方式、也可以采用自定义协议的方式。为了提高复用性,笔者将sqlite作为plugin的方式进行接入。
plugin工程
工程创建
详细可以参见tauri的文档:tauri.app/zh-cn/v1/gu…
pnpm tauri plugin init --name awesome
插件的详细代码可以参见github: github.com/rrkeji/rrai…
封装sqlite数据库的操作接口
- 首先是打开的数据库连接管理
可以使用tauri中提供的state方式进行管理
pub struct SqliteMap(Mutex<HashMap<String, Connection>>);
#[command]
pub async fn open(state: State<'_, SqliteMap>, path: String) -> Result<bool> {
}
可以使用文件的路径作为key。
也可以直接在内存中使用rust的静态变量维护一个全局的map
lazy_static! {
pub(crate) static ref CONNECTIONS: RwLock<HashMap<String, Arc<Mutex<Connection>>>> =
RwLock::new(HashMap::new());
}
- 查询接口封装
- 更新接口封装
- 关闭接口封装
#[command]
pub async fn open(path: String) -> Result<bool> {
rrai_desktop_sdk_common::sqlite::open(&path).await?;
Ok(true)
}
#[command]
pub async fn open_with_flags(path: String, iflags: i32) -> Result<bool> {
let flags = OpenFlags::default();
rrai_desktop_sdk_common::sqlite::open_with_flags(&path, flags).await?;
Ok(true)
}
#[command]
pub async fn close(path: String) -> Result<bool> {
rrai_desktop_sdk_common::sqlite::close(&path).await?;
Ok(true)
}
#[command]
pub async fn execute_sql(path: String, sql: String) -> Result<usize> {
let res = rrai_desktop_sdk_common::sqlite::execute_sql(&path, &sql).await?;
Ok(res)
}
#[command]
pub async fn execute_batch(path: String, sql: String) -> Result<bool> {
let res = rrai_desktop_sdk_common::sqlite::execute_batch(&path, &sql).await?;
Ok(res)
}
#[command]
pub async fn execute(path: String, sql: String, args: JsonValue) -> Result<usize> {
let res = rrai_desktop_sdk_common::sqlite::execute(&path, &sql, &args).await?;
Ok(res)
}
#[command]
pub async fn query_with_args(
path: String,
sql: String,
args: JsonValue,
) -> Result<Vec<HashMap<String, JsonValue>>> {
let res = rrai_desktop_sdk_common::sqlite::query_with_args(&path, &sql, &args).await?;
Ok(res)
}
/// Initializes the plugin.
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("rrai-sqlite")
.invoke_handler(tauri::generate_handler![
open,
open_with_flags,
query_with_args,
close,
execute_sql,
execute_batch,
execute
])
.setup(|app| {
// app.manage(SqliteMap::default());
Ok(())
})
.build()
}
如果使用tauri的state时,在init的setup中进行state的初始化。
详细的代码参见git github.com/rrkeji/rrai…
软软AI项目中引入plugin
tauri::Builder::default()
.setup(move |app| {
// let window = app.get_window("main").unwrap();
// set_shadow(&window, true).expect("Unsupported platform!");
#[cfg(debug_assertions)]
{
let window = app.get_window("main").unwrap();
window.open_devtools();
}
Ok(())
})
.plugin(rrai_desktop_sdk::plugins::storage::init())
.plugin(rrai_desktop_sdk::plugins::appbox::init())
.plugin(rrai_desktop_sdk::plugins::idns::init())
.plugin(rrai_desktop_sdk::plugins::sqlite::init())
.plugin(rrai_desktop_sdk::plugins::substrate::init())
.invoke_handler(tauri::generate_handler![
commands::system::cmds_system_process_exec
])
.register_uri_scheme_protocol("rrapp", rrai_desktop_sdk::plugins::appbox::rrapp_protocol)
.run(tauri::generate_context!())
.expect("error while running tauri application");
在软软AI的tauri主项目中,引用插件,主要是.plugin(rrai_desktop_sdk::plugins::sqlite::init()) 这句。
封装前端接口
import { invoke } from '@tauri-apps/api/tauri'
export class SQLite {
path: string
constructor(path: string) {
this.path = path
}
static async open(path: string): Promise<SQLite> {
let res = await invoke<string>('plugin:rrai-sqlite|open', { path });
console.log(res);
return new SQLite(path);
}
async close(): Promise<boolean> {
return await invoke('plugin:rrai-sqlite|close', { path: this.path })
}
async execute(sql: string, values?: Record<string, any>): Promise<boolean> {
return values ? await invoke('plugin:rrai-sqlite|execute', { path: this.path, sql, args: values }) : await invoke('plugin:sqlite|execute_sql', { path: this.path, sql })
}
async queryWithArgs<T>(sql: string, values?: Record<string, any>): Promise<T> {
return await invoke('plugin:rrai-sqlite|query_with_args', { path: this.path, sql, args: values ?? {} })
}
}
export default SQLite;
在ts中对应的插件中的方法进行封装。
调用示例
const dbversion = await SQLite.open(dbName);
//查询是否有该表
const rows = await dbversion.queryWithArgs<Array<{ count: number }>>("SELECT count(1) count FROM sqlite_master WHERE type='table' and name = 'databases_version' ");
console.log(rows);
if (!!rows && rows.length > 0 && rows[0].count > 0) {
} else {
//创建表
await dbversion.execute(`CREATE TABLE databases_version (name TEXT, version INTEGER, unique(name));`)
}
这样前端就可以不依赖后端工程师,直接操作数据库了。 关于数据库的版本问题,下一篇实现一个简单的数据库脚本版本控制的代码。
github:github.com/rrkeji/rrai…