snapshot of https://github.com/tauri-apps/plugins-workspace.git (v2, 2026-09-23; blobs > 131072B omitted)
Some checks failed
integration tests / run-integration-tests (windows-latest) (push) Has been cancelled
Test Rust / test (push) Has been cancelled
test plugins e2e (mobile) / ios (push) Has been cancelled
test plugins e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test plugins e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
integration tests / run-integration-tests (macos-latest) (push) Has been cancelled
version or publish / version-or-publish (push) Failing after 0s
integration tests / run-integration-tests (ubuntu-22.04) (push) Failing after 0s
Lint JavaScript / eslint (push) Failing after 0s
Sync / sync-to-mirrors (push) Failing after 0s
test plugins e2e (mobile) / android (push) Failing after 0s
test plugins e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
Test Rust / changes (push) Failing after 0s
Lint Rust / changes (push) Failing after 0s
Lint Rust / clippy (push) Has been skipped
Audit Rust / audit-rust (push) Failing after 1s
Audit JavaScript / audit-js (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-23 17:17:10 +05:00
commit 9ad64ec987
1469 changed files with 115067 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use indexmap::IndexMap;
use serde_json::Value as JsonValue;
use sqlx::migrate::Migrator;
use tauri::{command, AppHandle, Runtime, State};
use crate::{DbInstances, DbPool, Error, LastInsertId, Migrations};
#[command]
pub(crate) async fn load<R: Runtime>(
app: AppHandle<R>,
db_instances: State<'_, DbInstances>,
migrations: State<'_, Migrations>,
db: String,
) -> Result<String, crate::Error> {
let pool = DbPool::connect(&db, &app).await?;
if let Some(migrations) = migrations.0.lock().await.remove(&db) {
let migrator = Migrator::new(migrations).await?;
pool.migrate(&migrator).await?;
}
db_instances.0.write().await.insert(db.clone(), pool);
Ok(db)
}
/// Allows the database connection(s) to be closed; if no database
/// name is passed in then _all_ database connection pools will be
/// shut down.
#[command]
pub(crate) async fn close(
db_instances: State<'_, DbInstances>,
db: Option<String>,
) -> Result<bool, crate::Error> {
let instances = db_instances.0.read().await;
let pools = if let Some(db) = db {
vec![db]
} else {
instances.keys().cloned().collect()
};
for pool in pools {
let db = instances.get(&pool).ok_or(Error::DatabaseNotLoaded(pool))?;
db.close().await;
}
Ok(true)
}
/// Execute a command against the database
#[command]
pub(crate) async fn execute(
db_instances: State<'_, DbInstances>,
db: String,
query: String,
values: Vec<JsonValue>,
) -> Result<(u64, LastInsertId), crate::Error> {
let instances = db_instances.0.read().await;
let db = instances.get(&db).ok_or(Error::DatabaseNotLoaded(db))?;
db.execute(query, values).await
}
#[command]
pub(crate) async fn select(
db_instances: State<'_, DbInstances>,
db: String,
query: String,
values: Vec<JsonValue>,
) -> Result<Vec<IndexMap<String, JsonValue>>, crate::Error> {
let instances = db_instances.0.read().await;
let db = instances.get(&db).ok_or(Error::DatabaseNotLoaded(db))?;
db.select(query, values).await
}

View File

@@ -0,0 +1,10 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(feature = "mysql")]
pub(crate) mod mysql;
#[cfg(feature = "postgres")]
pub(crate) mod postgres;
#[cfg(feature = "sqlite")]
pub(crate) mod sqlite;

View File

@@ -0,0 +1,101 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde_json::Value as JsonValue;
use sqlx::{mysql::MySqlValueRef, TypeInfo, Value, ValueRef};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use crate::Error;
pub(crate) fn to_json(v: MySqlValueRef) -> Result<JsonValue, Error> {
if v.is_null() {
return Ok(JsonValue::Null);
}
let res = match v.type_info().name() {
"CHAR" | "VARCHAR" | "TINYTEXT" | "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "ENUM" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"FLOAT" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f32>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"DOUBLE" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f64>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"TINYINT" | "SMALLINT" | "INT" | "MEDIUMINT" | "BIGINT" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"TINYINT UNSIGNED" | "SMALLINT UNSIGNED" | "INT UNSIGNED" | "MEDIUMINT UNSIGNED"
| "BIGINT UNSIGNED" | "YEAR" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<u64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"BOOLEAN" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::Bool(v)
} else {
JsonValue::Null
}
}
"DATE" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Date>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Time>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"DATETIME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<PrimitiveDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIMESTAMP" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<OffsetDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"JSON" => ValueRef::to_owned(&v).try_decode().unwrap_or_default(),
"TINIYBLOB" | "MEDIUMBLOB" | "BLOB" | "LONGBLOB" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Vec<u8>>() {
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
} else {
JsonValue::Null
}
}
"NULL" => JsonValue::Null,
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
};
Ok(res)
}

View File

@@ -0,0 +1,136 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use rust_decimal::prelude::ToPrimitive;
use serde_json::Value as JsonValue;
use sqlx::{postgres::PgValueRef, TypeInfo, Value, ValueRef};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use uuid::Uuid;
use crate::Error;
pub(crate) fn to_json(v: PgValueRef) -> Result<JsonValue, Error> {
if v.is_null() {
return Ok(JsonValue::Null);
}
let res = match v.type_info().name() {
"CHAR" | "VARCHAR" | "TEXT" | "NAME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"UUID" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Uuid>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"FLOAT4" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f32>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"FLOAT8" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f64>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"INT2" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i16>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"INT4" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i32>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"INT8" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<i64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"BOOL" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::Bool(v)
} else {
JsonValue::Null
}
}
"DATE" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Date>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Time>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIMESTAMP" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<PrimitiveDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIMESTAMPTZ" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<OffsetDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"JSON" | "JSONB" => ValueRef::to_owned(&v).try_decode().unwrap_or_default(),
"BYTEA" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Vec<u8>>() {
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
} else {
JsonValue::Null
}
}
"NUMERIC" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<rust_decimal::Decimal>() {
if let Some(n) = v.to_f64().and_then(serde_json::Number::from_f64) {
JsonValue::Number(n)
} else {
JsonValue::String(v.to_string())
}
} else {
JsonValue::Null
}
}
"VOID" => JsonValue::Null,
// Handle custom types (enums, domains, etc.) by trying to decode as string
_ => {
let type_name = v.type_info().name().to_string();
if let Ok(v) = ValueRef::to_owned(&v).try_decode_unchecked::<String>() {
log::warn!("unsupported type {type_name} decoded as string");
JsonValue::String(v)
} else {
return Err(Error::UnsupportedDatatype(v.type_info().name().to_string()));
}
}
};
Ok(res)
}

View File

@@ -0,0 +1,78 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde_json::Value as JsonValue;
use sqlx::{sqlite::SqliteValueRef, TypeInfo, Value, ValueRef};
use time::{Date, PrimitiveDateTime, Time};
use crate::Error;
pub(crate) fn to_json(v: SqliteValueRef) -> Result<JsonValue, Error> {
if v.is_null() {
return Ok(JsonValue::Null);
}
let res = match v.type_info().name() {
"TEXT" => {
if let Ok(v) = v.to_owned().try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"REAL" => {
if let Ok(v) = v.to_owned().try_decode::<f64>() {
JsonValue::from(v)
} else {
JsonValue::Null
}
}
"INTEGER" | "NUMERIC" => {
if let Ok(v) = v.to_owned().try_decode::<i64>() {
JsonValue::Number(v.into())
} else {
JsonValue::Null
}
}
"BOOLEAN" => {
if let Ok(v) = v.to_owned().try_decode() {
JsonValue::Bool(v)
} else {
JsonValue::Null
}
}
"DATE" => {
if let Ok(v) = v.to_owned().try_decode::<Date>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"TIME" => {
if let Ok(v) = v.to_owned().try_decode::<Time>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"DATETIME" => {
if let Ok(v) = v.to_owned().try_decode::<PrimitiveDateTime>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"BLOB" => {
if let Ok(v) = v.to_owned().try_decode::<Vec<u8>>() {
JsonValue::Array(v.into_iter().map(|n| JsonValue::Number(n.into())).collect())
} else {
JsonValue::Null
}
}
"NULL" => JsonValue::Null,
_ => return Err(Error::UnsupportedDatatype(v.type_info().name().to_string())),
};
Ok(res)
}

42
plugins/sql/src/error.rs Normal file
View File

@@ -0,0 +1,42 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde::{Serialize, Serializer};
/// Errors that can happen while connecting to a database, running migrations
/// or executing a query.
///
/// Serializes to its [`std::fmt::Display`] representation, which is what the
/// frontend receives when a command fails.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// An error reported by [`sqlx`], such as a failed connection or a query the database rejected.
#[error(transparent)]
Sql(#[from] sqlx::Error),
/// A migration registered with [`crate::Builder::add_migrations`] could not be resolved or applied.
#[error(transparent)]
Migration(#[from] sqlx::migrate::MigrateError),
/// The connection string is missing its `scheme:` prefix, or the scheme does not
/// match any of the enabled database drivers. Contains the offending connection string.
#[error("invalid connection url: {0}")]
InvalidDbUrl(String),
/// The requested database has not been connected to with the `load` command
/// and is not listed in the plugin's `preload` configuration.
/// Contains the connection string of the database.
#[error("database {0} not loaded")]
DatabaseNotLoaded(String),
/// A value selected from the database has a SQL type that cannot be converted
/// to JSON. Contains the name of that SQL type.
#[error("unsupported datatype: {0}")]
UnsupportedDatatype(String),
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.to_string().as_ref())
}
}

252
plugins/sql/src/lib.rs Normal file
View File

@@ -0,0 +1,252 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
//! Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx).
//!
//! ## Cargo features
//!
//! No database driver is enabled by default. Enable at least one driver;
//! multiple drivers may be enabled together.
//!
//! - **sqlite**: Adds support for SQLite.
//! - **mysql**: Adds support for MySQL.
//! - **postgres**: Adds support for PostgreSQL.
#![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, RwLock};
use std::collections::HashMap;
/// The connection pools the plugin currently holds, keyed by the connection
/// string the database was loaded with.
///
/// It is managed as Tauri state, so Rust code can reach the pools with
/// [`tauri::Manager::state`] and run its own queries.
#[derive(Default)]
pub struct DbInstances(pub RwLock<HashMap<String, DbPool>>);
#[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<HashMap<String, MigrationList>>);
/// The `plugins > sql` section of the Tauri configuration file.
#[derive(Default, Clone, Deserialize)]
pub struct PluginConfig {
/// Connection strings of the databases to connect to when the application
/// starts. Empty by default.
#[serde(default)]
preload: Vec<String>,
}
/// The direction of a [`Migration`].
#[derive(Debug)]
pub enum MigrationKind {
/// Moves the schema forward. Only migrations of this kind are executed by the plugin.
Up,
/// Reverts an [`Up`](Self::Up) migration. Migrations of this kind are currently
/// never executed by the plugin.
Down,
}
impl From<MigrationKind> for MigrationType {
fn from(kind: MigrationKind) -> Self {
match kind {
MigrationKind::Up => Self::ReversibleUp,
MigrationKind::Down => Self::ReversibleDown,
}
}
}
/// A migration definition.
///
/// Migrations are attached to a database with [`Builder::add_migrations`] and run
/// the first time that database is connected to - on startup for the databases
/// listed in the `preload` configuration, otherwise when the frontend loads it.
/// Only [`MigrationKind::Up`] migrations are executed, in ascending
/// [`version`](Self::version) order, and sqlx keeps track of the versions that
/// already ran so each one is applied at most once per database.
#[derive(Debug)]
pub struct Migration {
/// The version of this migration. Determines the order in which migrations
/// run and identifies the migration in the database.
pub version: i64,
/// A human readable description of what the migration does.
pub description: &'static str,
/// The SQL executed when the migration runs.
pub sql: &'static str,
/// Whether this migration moves the schema forward or reverts it.
pub kind: MigrationKind,
}
#[derive(Debug)]
struct MigrationList(Vec<Migration>);
impl MigrationSource<'static> for MigrationList {
fn resolve(self) -> BoxFuture<'static, std::result::Result<Vec<SqlxMigration>, 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)
})
}
}
/// Allows blocking on async code without creating a nested runtime.
fn run_async_command<F: std::future::Future>(cmd: F) -> F::Output {
if tokio::runtime::Handle::try_current().is_ok() {
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(cmd))
} else {
tauri::async_runtime::block_on(cmd)
}
}
/// Tauri SQL plugin builder.
#[derive(Default)]
pub struct Builder {
migrations: Option<HashMap<String, MigrationList>>,
}
impl Builder {
/// Creates a new builder with no migrations registered.
///
/// Prints a message to stderr when none of the `sqlite`, `mysql` and
/// `postgres` Cargo features is enabled, since no database can be
/// connected to in that case.
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<Migration>) -> Self {
self.migrations
.get_or_insert(Default::default())
.insert(db_url.to_string(), MigrationList(migrations));
self
}
/// Builds the SQL plugin.
///
/// On setup the plugin connects to every database listed in the `preload`
/// array of its [configuration](PluginConfig), running the migrations
/// registered for them, and it closes all connection pools when the
/// application exits.
///
/// # Examples
///
/// ```
/// use tauri_plugin_sql::{Builder, Migration, MigrationKind};
///
/// fn sql_plugin<R: tauri::Runtime>(
/// ) -> tauri::plugin::TauriPlugin<R, Option<tauri_plugin_sql::PluginConfig>> {
/// Builder::new()
/// .add_migrations(
/// "sqlite:mydatabase.db",
/// vec![Migration {
/// version: 1,
/// description: "create todos table",
/// sql: "CREATE TABLE todos (id INTEGER PRIMARY KEY, title TEXT);",
/// kind: MigrationKind::Up,
/// }],
/// )
/// .build()
/// }
/// ```
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R, Option<PluginConfig>> {
PluginBuilder::<R, Option<PluginConfig>>::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();
run_async_command(async move {
let instances = DbInstances::default();
let mut lock = instances.0.write().await;
for db in config.preload {
let pool = DbPool::connect(&db, app).await?;
if let Some(migrations) =
self.migrations.as_mut().and_then(|mm| mm.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 {
run_async_command(async move {
let instances = &*app.state::<DbInstances>();
let instances = instances.0.read().await;
for value in instances.values() {
value.close().await;
}
});
}
})
.build()
}
}

343
plugins/sql/src/wrapper.rs Normal file
View File

@@ -0,0 +1,343 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#[cfg(feature = "sqlite")]
use std::fs::create_dir_all;
use indexmap::IndexMap;
use serde_json::Value as JsonValue;
#[cfg(any(feature = "sqlite", feature = "mysql", feature = "postgres"))]
use sqlx::{migrate::MigrateDatabase, Column, Executor, Pool, Row};
#[cfg(any(feature = "sqlite", feature = "mysql", feature = "postgres"))]
use tauri::Manager;
use tauri::{AppHandle, Runtime};
#[cfg(feature = "mysql")]
use sqlx::MySql;
#[cfg(feature = "postgres")]
use sqlx::Postgres;
#[cfg(feature = "sqlite")]
use sqlx::Sqlite;
use crate::LastInsertId;
/// A connection pool for one of the supported database drivers.
///
/// The variant is picked from the scheme of the connection string
/// (`sqlite:`, `mysql:` or `postgres:`) and only the variants whose Cargo
/// feature is enabled exist.
pub enum DbPool {
/// A SQLite connection pool. Only available with the `sqlite` Cargo feature.
#[cfg(feature = "sqlite")]
Sqlite(Pool<Sqlite>),
/// A MySQL connection pool. Only available with the `mysql` Cargo feature.
#[cfg(feature = "mysql")]
MySql(Pool<MySql>),
/// A PostgreSQL connection pool. Only available with the `postgres` Cargo feature.
#[cfg(feature = "postgres")]
Postgres(Pool<Postgres>),
/// Placeholder used when none of the `sqlite`, `mysql` and `postgres` Cargo
/// features is enabled. Connecting always fails and every other operation is a no-op.
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))]
None,
}
// public methods
/* impl DbPool {
/// Get the inner Sqlite Pool. Returns None for MySql and Postgres pools.
#[cfg(feature = "sqlite")]
pub fn sqlite(&self) -> Option<&Pool<Sqlite>> {
match self {
DbPool::Sqlite(pool) => Some(pool),
_ => None,
}
}
/// Get the inner MySql Pool. Returns None for Sqlite and Postgres pools.
#[cfg(feature = "mysql")]
pub fn mysql(&self) -> Option<&Pool<MySql>> {
match self {
DbPool::MySql(pool) => Some(pool),
_ => None,
}
}
/// Get the inner Postgres Pool. Returns None for MySql and Sqlite pools.
#[cfg(feature = "postgres")]
pub fn postgres(&self) -> Option<&Pool<Postgres>> {
match self {
DbPool::Postgres(pool) => Some(pool),
_ => None,
}
}
} */
// private methods
impl DbPool {
pub(crate) async fn connect<R: Runtime>(
conn_url: &str,
_app: &AppHandle<R>,
) -> Result<Self, crate::Error> {
match conn_url
.split_once(':')
.ok_or_else(|| crate::Error::InvalidDbUrl(conn_url.to_string()))?
.0
{
#[cfg(feature = "sqlite")]
"sqlite" => {
let app_path = _app
.path()
.app_config_dir()
.expect("No App config path was found!");
create_dir_all(&app_path).expect("Couldn't create app config dir");
let conn_url = &path_mapper(app_path, conn_url);
if !Sqlite::database_exists(conn_url).await.unwrap_or(false) {
Sqlite::create_database(conn_url).await?;
}
Ok(Self::Sqlite(Pool::connect(conn_url).await?))
}
#[cfg(feature = "mysql")]
"mysql" => {
if !MySql::database_exists(conn_url).await.unwrap_or(false) {
MySql::create_database(conn_url).await?;
}
Ok(Self::MySql(Pool::connect(conn_url).await?))
}
#[cfg(feature = "postgres")]
"postgres" => {
if !Postgres::database_exists(conn_url).await.unwrap_or(false) {
Postgres::create_database(conn_url).await?;
}
Ok(Self::Postgres(Pool::connect(conn_url).await?))
}
#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))]
_ => Err(crate::Error::InvalidDbUrl(format!(
"{conn_url} - No database driver enabled!"
))),
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
_ => Err(crate::Error::InvalidDbUrl(conn_url.to_string())),
}
}
pub(crate) async fn migrate(
&self,
_migrator: &sqlx::migrate::Migrator,
) -> Result<(), crate::Error> {
match self {
#[cfg(feature = "sqlite")]
DbPool::Sqlite(pool) => _migrator.run(pool).await?,
#[cfg(feature = "mysql")]
DbPool::MySql(pool) => _migrator.run(pool).await?,
#[cfg(feature = "postgres")]
DbPool::Postgres(pool) => _migrator.run(pool).await?,
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))]
DbPool::None => (),
}
Ok(())
}
pub(crate) async fn close(&self) {
match self {
#[cfg(feature = "sqlite")]
DbPool::Sqlite(pool) => pool.close().await,
#[cfg(feature = "mysql")]
DbPool::MySql(pool) => pool.close().await,
#[cfg(feature = "postgres")]
DbPool::Postgres(pool) => pool.close().await,
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))]
DbPool::None => (),
}
}
pub(crate) async fn execute(
&self,
_query: String,
_values: Vec<JsonValue>,
) -> Result<(u64, LastInsertId), crate::Error> {
Ok(match self {
#[cfg(feature = "sqlite")]
DbPool::Sqlite(pool) => {
let mut query = sqlx::query(&_query);
for value in _values {
if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned())
} else if let Some(number) = value.as_number() {
query = query.bind(number.as_f64().unwrap_or_default())
} else {
query = query.bind(value);
}
}
let result = pool.execute(query).await?;
(
result.rows_affected(),
LastInsertId::Sqlite(result.last_insert_rowid()),
)
}
#[cfg(feature = "mysql")]
DbPool::MySql(pool) => {
let mut query = sqlx::query(&_query);
for value in _values {
if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned())
} else if let Some(number) = value.as_number() {
query = query.bind(number.as_f64().unwrap_or_default())
} else {
query = query.bind(value);
}
}
let result = pool.execute(query).await?;
(
result.rows_affected(),
LastInsertId::MySql(result.last_insert_id()),
)
}
#[cfg(feature = "postgres")]
DbPool::Postgres(pool) => {
let mut query = sqlx::query(&_query);
for value in _values {
if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned())
} else if let Some(number) = value.as_number() {
query = query.bind(number.as_f64().unwrap_or_default())
} else {
query = query.bind(value);
}
}
let result = pool.execute(query).await?;
(result.rows_affected(), LastInsertId::Postgres(()))
}
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))]
DbPool::None => (0, LastInsertId::None),
})
}
pub(crate) async fn select(
&self,
_query: String,
_values: Vec<JsonValue>,
) -> Result<Vec<IndexMap<String, JsonValue>>, crate::Error> {
Ok(match self {
#[cfg(feature = "sqlite")]
DbPool::Sqlite(pool) => {
let mut query = sqlx::query(&_query);
for value in _values {
if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned())
} else if let Some(number) = value.as_number() {
query = query.bind(number.as_f64().unwrap_or_default())
} else {
query = query.bind(value);
}
}
let rows = pool.fetch_all(query).await?;
let mut values = Vec::new();
for row in rows {
let mut value = IndexMap::default();
for (i, column) in row.columns().iter().enumerate() {
let v = row.try_get_raw(i)?;
let v = crate::decode::sqlite::to_json(v)?;
value.insert(column.name().to_string(), v);
}
values.push(value);
}
values
}
#[cfg(feature = "mysql")]
DbPool::MySql(pool) => {
let mut query = sqlx::query(&_query);
for value in _values {
if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned())
} else if let Some(number) = value.as_number() {
query = query.bind(number.as_f64().unwrap_or_default())
} else {
query = query.bind(value);
}
}
let rows = pool.fetch_all(query).await?;
let mut values = Vec::new();
for row in rows {
let mut value = IndexMap::default();
for (i, column) in row.columns().iter().enumerate() {
let v = row.try_get_raw(i)?;
let v = crate::decode::mysql::to_json(v)?;
value.insert(column.name().to_string(), v);
}
values.push(value);
}
values
}
#[cfg(feature = "postgres")]
DbPool::Postgres(pool) => {
let mut query = sqlx::query(&_query);
for value in _values {
if value.is_null() {
query = query.bind(None::<JsonValue>);
} else if value.is_string() {
query = query.bind(value.as_str().unwrap().to_owned())
} else if let Some(number) = value.as_number() {
query = query.bind(number.as_f64().unwrap_or_default())
} else {
query = query.bind(value);
}
}
let rows = pool.fetch_all(query).await?;
let mut values = Vec::new();
for row in rows {
let mut value = IndexMap::default();
for (i, column) in row.columns().iter().enumerate() {
let v = row.try_get_raw(i)?;
let v = crate::decode::postgres::to_json(v)?;
value.insert(column.name().to_string(), v);
}
values.push(value);
}
values
}
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))]
DbPool::None => Vec::new(),
})
}
}
#[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: std::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!")
)
}