From 9c7d23355604283481e4e37c92506a3fbfd99833 Mon Sep 17 00:00:00 2001 From: FabianLars Date: Thu, 16 Feb 2023 18:26:11 +0100 Subject: [PATCH] feat(sql): support absolute paths and basedirectory for sqlite --- plugins/sql/guest-js/index.ts | 14 ++++-- plugins/sql/src/plugin.rs | 85 +++++++++++++++++++++-------------- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/plugins/sql/guest-js/index.ts b/plugins/sql/guest-js/index.ts index a574e72e..0c3bfbb1 100644 --- a/plugins/sql/guest-js/index.ts +++ b/plugins/sql/guest-js/index.ts @@ -1,4 +1,5 @@ import { invoke } from "@tauri-apps/api/tauri"; +import { BaseDirectory } from "@tauri-apps/api/path" export interface QueryResult { /** The number of rows affected by the query. */ @@ -34,16 +35,20 @@ export default class Database { * * # Sqlite * - * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * The path must be in one of the following formats: + * - `sqlite::memory:`: Open an in-memory database. + * - `sqlite:///data.db`: Open the file `data.db` from the root directory. `sqlite://\\?\` will be automatically converted to `sqlite:///` + * - `sqlite://data.db`: Open the file `data.db` relative to the `dir` argument, or BaseDirectory.App if not provided * * @example * ```ts * const db = await Database.load("sqlite:test.db"); * ``` */ - static async load(path: string): Promise { + static async load(path: string, dir?: BaseDirectory): Promise { const _path = await invoke("plugin:sql|load", { db: path, + dir }); return new Database(_path); @@ -58,7 +63,10 @@ export default class Database { * * # Sqlite * - * The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`. + * The path must be in one of the following formats: + * - `sqlite::memory:`: Open an in-memory database. + * - `sqlite:///data.db`: Open the file `data.db` from the root directory. `sqlite://\\?\` will be automatically converted to `sqlite:///` + * - `sqlite://data.db`: Open the file `data.db` relative to the `dir` argument, or BaseDirectory.App if not provided * * @example * ```ts diff --git a/plugins/sql/src/plugin.rs b/plugins/sql/src/plugin.rs index c33e81e6..02a6a3b5 100644 --- a/plugins/sql/src/plugin.rs +++ b/plugins/sql/src/plugin.rs @@ -22,7 +22,9 @@ use tokio::sync::Mutex; use std::collections::HashMap; #[cfg(feature = "sqlite")] -use std::{fs::create_dir_all, path::PathBuf}; +use std::fs::create_dir_all; +#[cfg(feature = "sqlite")] +use tauri::api::path::BaseDirectory; #[cfg(feature = "sqlite")] type Db = sqlx::sqlite::Sqlite; @@ -46,6 +48,13 @@ pub enum Error { DatabaseNotLoaded(String), #[error("unsupported datatype: {0}")] UnsupportedDatatype(String), + + #[cfg(feature = "sqlite")] + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(feature = "sqlite")] + #[error(transparent)] + Tauri(#[from] tauri::api::Error), } impl Serialize for Error { @@ -59,33 +68,48 @@ impl Serialize for Error { type Result = std::result::Result; -#[cfg(feature = "sqlite")] -/// Resolves the App's **file path** from the `AppHandle` context -/// object -fn app_path(app: &AppHandle) -> PathBuf { - #[allow(deprecated)] // FIXME: Change to non-deprecated function in Tauri v2 - app.path_resolver() - .app_dir() - .expect("No App path was found!") -} - #[cfg(feature = "sqlite")] /// Maps the user supplied DB connection string to a connection string /// with a fully qualified file path to the App's designed "app_path" -fn path_mapper(mut app_path: PathBuf, connection_string: &str) -> String { - app_path.push( - connection_string - .split_once(':') - .expect("Couldn't parse the connection string for DB!") - .1, - ); - - format!( - "sqlite:{}", - app_path - .to_str() - .expect("Problem creating fully qualified path to Database file!") - ) +fn path_mapper( + app: &AppHandle, + connection_string: &str, + dir: Option, +) -> Result { + use tauri::api::path::resolve_path; + + if connection_string.starts_with("sqlite::memory:") { + return Ok(connection_string.to_string()); + } + if connection_string.starts_with("sqlite:///") + || connection_string.starts_with(r"sqlite://\\?\") + { + create_dir_all( + connection_string + .replace("sqlite:///", "/") + .replace(r"sqlite://\\?\", "") + .replace("?mode=ro", ""), + )?; + return Ok(connection_string.replace(r"\\?\", "/").replace('\\', "/")); + } + + let connection_string = connection_string + .replace("sqlite://", "") + .replace("sqlite:", ""); + + let path = resolve_path( + &app.config(), + app.package_info(), + &app.env(), + connection_string, + #[allow(deprecated)] // FIXME: Use non deprecated variant in tauri v2 + dir.or(Some(BaseDirectory::App)), + )?; + + Ok(format!( + "sqlite://{}", + path.display().to_string().replace(r"\\?\", "/") + )) } #[derive(Default)] @@ -151,15 +175,13 @@ async fn load( db_instances: State<'_, DbInstances>, migrations: State<'_, Migrations>, db: String, + dir: Option, ) -> Result { #[cfg(feature = "sqlite")] - let fqdb = path_mapper(app_path(&app), &db); + let fqdb = path_mapper(&app, &db, dir)?; #[cfg(not(feature = "sqlite"))] let fqdb = db.clone(); - #[cfg(feature = "sqlite")] - create_dir_all(app_path(&app)).expect("Problem creating App directory!"); - if !Db::database_exists(&fqdb).await.unwrap_or(false) { Db::create_database(&fqdb).await?; } @@ -334,15 +356,12 @@ impl Builder { .setup_with_config(|app, config: Option| { let config = config.unwrap_or_default(); - #[cfg(feature = "sqlite")] - create_dir_all(app_path(app)).expect("problems creating App directory!"); - tauri::async_runtime::block_on(async move { let instances = DbInstances::default(); let mut lock = instances.0.lock().await; for db in config.preload { #[cfg(feature = "sqlite")] - let fqdb = path_mapper(app_path(app), &db); + let fqdb = path_mapper(app, &db, None)?; #[cfg(not(feature = "sqlite"))] let fqdb = db.clone();