// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT //! [![](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/sql/banner.png)](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/sql) //! //! Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx). It supports the `sqlite`, `mysql` and `postgres` drivers, enabled by a Cargo feature. #![doc( html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png", html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png" )] mod commands; mod decode; mod error; mod wrapper; pub use error::Error; pub use wrapper::DbPool; use futures_core::future::BoxFuture; use serde::{Deserialize, Serialize}; use sqlx::{ error::BoxDynError, migrate::{Migration as SqlxMigration, MigrationSource, MigrationType, Migrator}, }; use tauri::{ plugin::{Builder as PluginBuilder, TauriPlugin}, Manager, RunEvent, Runtime, }; use tokio::sync::Mutex; use std::collections::HashMap; #[derive(Default)] pub struct DbInstances(pub Mutex>); #[derive(Serialize)] #[serde(untagged)] pub(crate) enum LastInsertId { #[cfg(feature = "sqlite")] Sqlite(i64), #[cfg(feature = "mysql")] MySql(u64), #[cfg(feature = "postgres")] Postgres(()), #[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))] None, } struct Migrations(Mutex>); #[derive(Default, Clone, Deserialize)] pub struct PluginConfig { #[serde(default)] preload: Vec, } #[derive(Debug)] pub enum MigrationKind { Up, Down, } impl From for MigrationType { fn from(kind: MigrationKind) -> Self { match kind { MigrationKind::Up => Self::ReversibleUp, MigrationKind::Down => Self::ReversibleDown, } } } /// A migration definition. #[derive(Debug)] pub struct Migration { pub version: i64, pub description: &'static str, pub sql: &'static str, pub kind: MigrationKind, } #[derive(Debug)] struct MigrationList(Vec); impl MigrationSource<'static> for MigrationList { fn resolve(self) -> BoxFuture<'static, std::result::Result, BoxDynError>> { Box::pin(async move { let mut migrations = Vec::new(); for migration in self.0 { if matches!(migration.kind, MigrationKind::Up) { migrations.push(SqlxMigration::new( migration.version, migration.description.into(), migration.kind.into(), migration.sql.into(), false, )); } } Ok(migrations) }) } } /// Tauri SQL plugin builder. #[derive(Default)] pub struct Builder { migrations: Option>, } impl Builder { pub fn new() -> Self { #[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))] eprintln!("No sql driver enabled. Please set at least one of the \"sqlite\", \"mysql\", \"postgres\" feature flags."); Self::default() } /// Add migrations to a database. #[must_use] pub fn add_migrations(mut self, db_url: &str, migrations: Vec) -> Self { self.migrations .get_or_insert(Default::default()) .insert(db_url.to_string(), MigrationList(migrations)); self } pub fn build(mut self) -> TauriPlugin> { PluginBuilder::>::new("sql") .invoke_handler(tauri::generate_handler![ commands::load, commands::execute, commands::select, commands::close ]) .setup(|app, api| { let config = api.config().clone().unwrap_or_default(); tauri::async_runtime::block_on(async move { let instances = DbInstances::default(); let mut lock = instances.0.lock().await; for db in config.preload { let pool = DbPool::connect(&db, app).await?; if let Some(migrations) = self.migrations.as_mut().unwrap().remove(&db) { let migrator = Migrator::new(migrations).await?; pool.migrate(&migrator).await?; } lock.insert(db, pool); } drop(lock); app.manage(instances); app.manage(Migrations(Mutex::new( self.migrations.take().unwrap_or_default(), ))); Ok(()) }) }) .on_event(|app, event| { if let RunEvent::Exit = event { tauri::async_runtime::block_on(async move { let instances = &*app.state::(); let instances = instances.0.lock().await; for value in instances.values() { value.close().await; } }); } }) .build() } }