mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-04-25 11:46:06 +02:00
copy plugin sources
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "tauri-plugin-sql"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tauri.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "json"] }
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
futures = "0.3"
|
||||
|
||||
[features]
|
||||
sqlite = ["sqlx/sqlite"]
|
||||
mysql = ["sqlx/mysql"]
|
||||
postgres = ["sqlx/postgres"]
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
var d=Object.defineProperty;var e=(c,a)=>{for(var b in a)d(c,b,{get:a[b],enumerable:!0});};
|
||||
|
||||
var f={};e(f,{convertFileSrc:()=>w,invoke:()=>c,transformCallback:()=>s});function u(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function s(e,r=!1){let n=u(),t=`_${n}`;return Object.defineProperty(window,t,{value:o=>(r&&Reflect.deleteProperty(window,t),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function c(e,r={}){return new Promise((n,t)=>{let o=s(i=>{n(i),Reflect.deleteProperty(window,`_${a}`);},!0),a=s(i=>{t(i),Reflect.deleteProperty(window,`_${o}`);},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:a,...r});})}function w(e,r="asset"){let n=encodeURIComponent(e);return navigator.userAgent.includes("Windows")?`https://${r}.localhost/${n}`:`${r}://localhost/${n}`}
|
||||
|
||||
/**
|
||||
* **Database**
|
||||
*
|
||||
* The `Database` class serves as the primary interface for
|
||||
* communicating with the rust side of the sql plugin.
|
||||
*/
|
||||
class Database {
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
}
|
||||
/**
|
||||
* **load**
|
||||
*
|
||||
* A static initializer which connects to the underlying database and
|
||||
* returns a `Database` instance once a connection to the database is established.
|
||||
*
|
||||
* # Sqlite
|
||||
*
|
||||
* The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = await Database.load("sqlite:test.db");
|
||||
* ```
|
||||
*/
|
||||
static async load(path) {
|
||||
const _path = await c("plugin:sql|load", {
|
||||
db: path,
|
||||
});
|
||||
return new Database(_path);
|
||||
}
|
||||
/**
|
||||
* **get**
|
||||
*
|
||||
* A static initializer which synchronously returns an instance of
|
||||
* the Database class while deferring the actual database connection
|
||||
* until the first invocation or selection on the database.
|
||||
*
|
||||
* # Sqlite
|
||||
*
|
||||
* The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = Database.get("sqlite:test.db");
|
||||
* ```
|
||||
*/
|
||||
static get(path) {
|
||||
return new Database(path);
|
||||
}
|
||||
/**
|
||||
* **execute**
|
||||
*
|
||||
* Passes a SQL expression to the database for execution.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await db.execute(
|
||||
* "UPDATE todos SET title = $1, completed = $2 WHERE id = $3",
|
||||
* [ todos.title, todos.status, todos.id ]
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async execute(query, bindValues) {
|
||||
const [rowsAffected, lastInsertId] = await c("plugin:sql|execute", {
|
||||
db: this.path,
|
||||
query,
|
||||
values: bindValues !== null && bindValues !== void 0 ? bindValues : [],
|
||||
});
|
||||
return {
|
||||
lastInsertId,
|
||||
rowsAffected,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* **select**
|
||||
*
|
||||
* Passes in a SELECT query to the database for execution.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await db.select(
|
||||
* "SELECT * from todos WHERE id = $1", id
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async select(query, bindValues) {
|
||||
const result = await c("plugin:sql|select", {
|
||||
db: this.path,
|
||||
query,
|
||||
values: bindValues !== null && bindValues !== void 0 ? bindValues : [],
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* **close**
|
||||
*
|
||||
* Closes the database connection pool.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const success = await db.close()
|
||||
* ```
|
||||
* @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope.
|
||||
*/
|
||||
async close(db) {
|
||||
const success = await c("plugin:sql|close", {
|
||||
db,
|
||||
});
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
export { Database as default };
|
||||
//# sourceMappingURL=index.min.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.min.js","sources":["../../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-FEIY7W7S.js","../../../../node_modules/.pnpm/@tauri-apps+api@1.2.0/node_modules/@tauri-apps/api/chunk-RCPA6UVN.js","../index.ts"],"sourcesContent":["var d=Object.defineProperty;var e=(c,a)=>{for(var b in a)d(c,b,{get:a[b],enumerable:!0})};export{e as a};\n","import{a as d}from\"./chunk-FEIY7W7S.js\";var f={};d(f,{convertFileSrc:()=>w,invoke:()=>c,transformCallback:()=>s});function u(){return window.crypto.getRandomValues(new Uint32Array(1))[0]}function s(e,r=!1){let n=u(),t=`_${n}`;return Object.defineProperty(window,t,{value:o=>(r&&Reflect.deleteProperty(window,t),e==null?void 0:e(o)),writable:!1,configurable:!0}),n}async function c(e,r={}){return new Promise((n,t)=>{let o=s(i=>{n(i),Reflect.deleteProperty(window,`_${a}`)},!0),a=s(i=>{t(i),Reflect.deleteProperty(window,`_${o}`)},!0);window.__TAURI_IPC__({cmd:e,callback:o,error:a,...r})})}function w(e,r=\"asset\"){let n=encodeURIComponent(e);return navigator.userAgent.includes(\"Windows\")?`https://${r}.localhost/${n}`:`${r}://localhost/${n}`}export{s as a,c as b,w as c,f as d};\n",null],"names":["d","invoke"],"mappings":"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"}
|
||||
Vendored
+117
@@ -0,0 +1,117 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
/**
|
||||
* **Database**
|
||||
*
|
||||
* The `Database` class serves as the primary interface for
|
||||
* communicating with the rust side of the sql plugin.
|
||||
*/
|
||||
class Database {
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
}
|
||||
/**
|
||||
* **load**
|
||||
*
|
||||
* A static initializer which connects to the underlying database and
|
||||
* returns a `Database` instance once a connection to the database is established.
|
||||
*
|
||||
* # Sqlite
|
||||
*
|
||||
* The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = await Database.load("sqlite:test.db");
|
||||
* ```
|
||||
*/
|
||||
static async load(path) {
|
||||
const _path = await invoke("plugin:sql|load", {
|
||||
db: path,
|
||||
});
|
||||
return new Database(_path);
|
||||
}
|
||||
/**
|
||||
* **get**
|
||||
*
|
||||
* A static initializer which synchronously returns an instance of
|
||||
* the Database class while deferring the actual database connection
|
||||
* until the first invocation or selection on the database.
|
||||
*
|
||||
* # Sqlite
|
||||
*
|
||||
* The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = Database.get("sqlite:test.db");
|
||||
* ```
|
||||
*/
|
||||
static get(path) {
|
||||
return new Database(path);
|
||||
}
|
||||
/**
|
||||
* **execute**
|
||||
*
|
||||
* Passes a SQL expression to the database for execution.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await db.execute(
|
||||
* "UPDATE todos SET title = $1, completed = $2 WHERE id = $3",
|
||||
* [ todos.title, todos.status, todos.id ]
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async execute(query, bindValues) {
|
||||
const [rowsAffected, lastInsertId] = await invoke("plugin:sql|execute", {
|
||||
db: this.path,
|
||||
query,
|
||||
values: bindValues !== null && bindValues !== void 0 ? bindValues : [],
|
||||
});
|
||||
return {
|
||||
lastInsertId,
|
||||
rowsAffected,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* **select**
|
||||
*
|
||||
* Passes in a SELECT query to the database for execution.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await db.select(
|
||||
* "SELECT * from todos WHERE id = $1", id
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async select(query, bindValues) {
|
||||
const result = await invoke("plugin:sql|select", {
|
||||
db: this.path,
|
||||
query,
|
||||
values: bindValues !== null && bindValues !== void 0 ? bindValues : [],
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* **close**
|
||||
*
|
||||
* Closes the database connection pool.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const success = await db.close()
|
||||
* ```
|
||||
* @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope.
|
||||
*/
|
||||
async close(db) {
|
||||
const success = await invoke("plugin:sql|close", {
|
||||
db,
|
||||
});
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
export { Database as default };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../index.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAgBA;;;;;AAKG;AACW,MAAO,QAAQ,CAAA;AAE3B,IAAA,WAAA,CAAY,IAAY,EAAA;AACtB,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;KAClB;AAED;;;;;;;;;;;;;;AAcG;AACH,IAAA,aAAa,IAAI,CAAC,IAAY,EAAA;AAC5B,QAAA,MAAM,KAAK,GAAG,MAAM,MAAM,CAAS,iBAAiB,EAAE;AACpD,YAAA,EAAE,EAAE,IAAI;AACT,SAAA,CAAC,CAAC;AAEH,QAAA,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC;KAC5B;AAED;;;;;;;;;;;;;;;AAeG;IACH,OAAO,GAAG,CAAC,IAAY,EAAA;AACrB,QAAA,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;KAC3B;AAED;;;;;;;;;;;;AAYG;AACH,IAAA,MAAM,OAAO,CAAC,KAAa,EAAE,UAAsB,EAAA;QACjD,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,GAAG,MAAM,MAAM,CAC/C,oBAAoB,EACpB;YACE,EAAE,EAAE,IAAI,CAAC,IAAI;YACb,KAAK;AACL,YAAA,MAAM,EAAE,UAAU,KAAA,IAAA,IAAV,UAAU,KAAV,KAAA,CAAA,GAAA,UAAU,GAAI,EAAE;AACzB,SAAA,CACF,CAAC;QAEF,OAAO;YACL,YAAY;YACZ,YAAY;SACb,CAAC;KACH;AAED;;;;;;;;;;;AAWG;AACH,IAAA,MAAM,MAAM,CAAI,KAAa,EAAE,UAAsB,EAAA;AACnD,QAAA,MAAM,MAAM,GAAG,MAAM,MAAM,CAAI,mBAAmB,EAAE;YAClD,EAAE,EAAE,IAAI,CAAC,IAAI;YACb,KAAK;AACL,YAAA,MAAM,EAAE,UAAU,KAAA,IAAA,IAAV,UAAU,KAAV,KAAA,CAAA,GAAA,UAAU,GAAI,EAAE;AACzB,SAAA,CAAC,CAAC;AAEH,QAAA,OAAO,MAAM,CAAC;KACf;AAED;;;;;;;;;;AAUG;IACH,MAAM,KAAK,CAAC,EAAW,EAAA;AACrB,QAAA,MAAM,OAAO,GAAG,MAAM,MAAM,CAAU,kBAAkB,EAAE;YACxD,EAAE;AACH,SAAA,CAAC,CAAC;AACH,QAAA,OAAO,OAAO,CAAC;KAChB;AACF;;;;"}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
|
||||
export interface QueryResult {
|
||||
/** The number of rows affected by the query. */
|
||||
rowsAffected: number;
|
||||
/**
|
||||
* The last inserted `id`.
|
||||
*
|
||||
* This value is always `0` when using the Postgres driver. If the
|
||||
* last inserted id is required on Postgres, the `select` function
|
||||
* must be used, with a `RETURNING` clause
|
||||
* (`INSERT INTO todos (title) VALUES ($1) RETURNING id`).
|
||||
*/
|
||||
lastInsertId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Database**
|
||||
*
|
||||
* The `Database` class serves as the primary interface for
|
||||
* communicating with the rust side of the sql plugin.
|
||||
*/
|
||||
export default class Database {
|
||||
path: string;
|
||||
constructor(path: string) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* **load**
|
||||
*
|
||||
* A static initializer which connects to the underlying database and
|
||||
* returns a `Database` instance once a connection to the database is established.
|
||||
*
|
||||
* # Sqlite
|
||||
*
|
||||
* The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = await Database.load("sqlite:test.db");
|
||||
* ```
|
||||
*/
|
||||
static async load(path: string): Promise<Database> {
|
||||
const _path = await invoke<string>("plugin:sql|load", {
|
||||
db: path,
|
||||
});
|
||||
|
||||
return new Database(_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* **get**
|
||||
*
|
||||
* A static initializer which synchronously returns an instance of
|
||||
* the Database class while deferring the actual database connection
|
||||
* until the first invocation or selection on the database.
|
||||
*
|
||||
* # Sqlite
|
||||
*
|
||||
* The path is relative to `tauri::api::path::BaseDirectory::App` and must start with `sqlite:`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = Database.get("sqlite:test.db");
|
||||
* ```
|
||||
*/
|
||||
static get(path: string): Database {
|
||||
return new Database(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* **execute**
|
||||
*
|
||||
* Passes a SQL expression to the database for execution.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await db.execute(
|
||||
* "UPDATE todos SET title = $1, completed = $2 WHERE id = $3",
|
||||
* [ todos.title, todos.status, todos.id ]
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async execute(query: string, bindValues?: unknown[]): Promise<QueryResult> {
|
||||
const [rowsAffected, lastInsertId] = await invoke<[number, number]>(
|
||||
"plugin:sql|execute",
|
||||
{
|
||||
db: this.path,
|
||||
query,
|
||||
values: bindValues ?? [],
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
lastInsertId,
|
||||
rowsAffected,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* **select**
|
||||
*
|
||||
* Passes in a SELECT query to the database for execution.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const result = await db.select(
|
||||
* "SELECT * from todos WHERE id = $1", id
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
async select<T>(query: string, bindValues?: unknown[]): Promise<T> {
|
||||
const result = await invoke<T>("plugin:sql|select", {
|
||||
db: this.path,
|
||||
query,
|
||||
values: bindValues ?? [],
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* **close**
|
||||
*
|
||||
* Closes the database connection pool.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const success = await db.close()
|
||||
* ```
|
||||
* @param db - Optionally state the name of a database if you are managing more than one. Otherwise, all database pools will be in scope.
|
||||
*/
|
||||
async close(db?: string): Promise<boolean> {
|
||||
const success = await invoke<boolean>("plugin:sql|close", {
|
||||
db,
|
||||
});
|
||||
return success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "tauri-plugin-{{name}}-api",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT or APACHE-2.0",
|
||||
"type": "module",
|
||||
"browser": "dist/index.min.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"browser": "./dist/index.min.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/*.map",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
import { createConfig } from "../../../shared/rollup.config.mjs";
|
||||
|
||||
export default createConfig({
|
||||
pkg: JSON.parse(
|
||||
readFileSync(new URL("./package.json", import.meta.url), "utf8")
|
||||
),
|
||||
external: [/^@tauri-apps\/api/],
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../shared/tsconfig.json
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "sqlite", feature = "mysql"),
|
||||
all(feature = "sqlite", feature = "postgres"),
|
||||
all(feature = "mysql", feature = "postgres")
|
||||
))]
|
||||
compile_error!("Only one database driver can be enabled. Use `default-features = false` and set the feature flag for the driver of your choice.");
|
||||
|
||||
#[cfg(not(any(feature = "sqlite", feature = "mysql", feature = "postgres")))]
|
||||
compile_error!(
|
||||
"Database driver not defined. Please set the feature flag for the driver of your choice."
|
||||
);
|
||||
|
||||
#[cfg(any(
|
||||
all(feature = "sqlite", not(any(feature = "mysql", feature = "postgres"))),
|
||||
all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))),
|
||||
all(feature = "postgres", not(any(feature = "sqlite", feature = "mysql"))),
|
||||
))]
|
||||
mod plugin;
|
||||
#[cfg(any(
|
||||
all(feature = "sqlite", not(any(feature = "mysql", feature = "postgres"))),
|
||||
all(feature = "mysql", not(any(feature = "sqlite", feature = "postgres"))),
|
||||
all(feature = "postgres", not(any(feature = "sqlite", feature = "mysql"))),
|
||||
))]
|
||||
pub use plugin::*;
|
||||
@@ -0,0 +1,387 @@
|
||||
// Copyright 2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use serde::{ser::Serializer, Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use sqlx::{
|
||||
error::BoxDynError,
|
||||
migrate::{
|
||||
MigrateDatabase, Migration as SqlxMigration, MigrationSource, MigrationType, Migrator,
|
||||
},
|
||||
Column, Pool, Row, TypeInfo,
|
||||
};
|
||||
use tauri::{
|
||||
command,
|
||||
plugin::{Plugin, Result as PluginResult},
|
||||
AppHandle, Invoke, Manager, RunEvent, Runtime, State,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
use std::{fs::create_dir_all, path::PathBuf};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
type Db = sqlx::sqlite::Sqlite;
|
||||
#[cfg(feature = "mysql")]
|
||||
type Db = sqlx::mysql::MySql;
|
||||
#[cfg(feature = "postgres")]
|
||||
type Db = sqlx::postgres::Postgres;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
type LastInsertId = i64;
|
||||
#[cfg(not(feature = "sqlite"))]
|
||||
type LastInsertId = u64;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Sql(#[from] sqlx::Error),
|
||||
#[error(transparent)]
|
||||
Migration(#[from] sqlx::migrate::MigrateError),
|
||||
#[error("database {0} not loaded")]
|
||||
DatabaseNotLoaded(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())
|
||||
}
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
/// Resolves the App's **file path** from the `AppHandle` context
|
||||
/// object
|
||||
fn app_path<R: Runtime>(app: &AppHandle<R>) -> 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!")
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DbInstances(Mutex<HashMap<String, Pool<Db>>>);
|
||||
|
||||
struct Migrations(Mutex<HashMap<String, MigrationList>>);
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
struct PluginConfig {
|
||||
#[serde(default)]
|
||||
preload: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MigrationKind {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl From<MigrationKind> 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<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(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(migrations)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn load<R: Runtime>(
|
||||
#[allow(unused_variables)] app: AppHandle<R>,
|
||||
db_instances: State<'_, DbInstances>,
|
||||
migrations: State<'_, Migrations>,
|
||||
db: String,
|
||||
) -> Result<String> {
|
||||
#[cfg(feature = "sqlite")]
|
||||
let fqdb = path_mapper(app_path(&app), &db);
|
||||
#[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?;
|
||||
}
|
||||
let pool = Pool::connect(&fqdb).await?;
|
||||
|
||||
if let Some(migrations) = migrations.0.lock().await.remove(&db) {
|
||||
let migrator = Migrator::new(migrations).await?;
|
||||
migrator.run(&pool).await?;
|
||||
}
|
||||
|
||||
db_instances.0.lock().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]
|
||||
async fn close(db_instances: State<'_, DbInstances>, db: Option<String>) -> Result<bool> {
|
||||
let mut instances = db_instances.0.lock().await;
|
||||
|
||||
let pools = if let Some(db) = db {
|
||||
vec![db]
|
||||
} else {
|
||||
instances.keys().cloned().collect()
|
||||
};
|
||||
|
||||
for pool in pools {
|
||||
let db = instances
|
||||
.get_mut(&pool) //
|
||||
.ok_or(Error::DatabaseNotLoaded(pool))?;
|
||||
db.close().await;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Execute a command against the database
|
||||
#[command]
|
||||
async fn execute(
|
||||
db_instances: State<'_, DbInstances>,
|
||||
db: String,
|
||||
query: String,
|
||||
values: Vec<JsonValue>,
|
||||
) -> Result<(u64, LastInsertId)> {
|
||||
let mut instances = db_instances.0.lock().await;
|
||||
|
||||
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
||||
let mut query = sqlx::query(&query);
|
||||
for value in values {
|
||||
if value.is_string() {
|
||||
query = query.bind(value.as_str().unwrap().to_owned())
|
||||
} else {
|
||||
query = query.bind(value);
|
||||
}
|
||||
}
|
||||
let result = query.execute(&*db).await?;
|
||||
#[cfg(feature = "sqlite")]
|
||||
let r = Ok((result.rows_affected(), result.last_insert_rowid()));
|
||||
#[cfg(feature = "mysql")]
|
||||
let r = Ok((result.rows_affected(), result.last_insert_id()));
|
||||
#[cfg(feature = "postgres")]
|
||||
let r = Ok((result.rows_affected(), 0));
|
||||
r
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn select(
|
||||
db_instances: State<'_, DbInstances>,
|
||||
db: String,
|
||||
query: String,
|
||||
values: Vec<JsonValue>,
|
||||
) -> Result<Vec<HashMap<String, JsonValue>>> {
|
||||
let mut instances = db_instances.0.lock().await;
|
||||
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
||||
let mut query = sqlx::query(&query);
|
||||
for value in values {
|
||||
if value.is_string() {
|
||||
query = query.bind(value.as_str().unwrap().to_owned())
|
||||
} else {
|
||||
query = query.bind(value);
|
||||
}
|
||||
}
|
||||
let rows = query.fetch_all(&*db).await?;
|
||||
let mut values = Vec::new();
|
||||
for row in rows {
|
||||
let mut value = HashMap::default();
|
||||
for (i, column) in row.columns().iter().enumerate() {
|
||||
let info = column.type_info();
|
||||
let v = if info.is_null() {
|
||||
JsonValue::Null
|
||||
} else {
|
||||
match info.name() {
|
||||
"VARCHAR" | "STRING" | "TEXT" | "DATETIME" => {
|
||||
if let Ok(s) = row.try_get(i) {
|
||||
JsonValue::String(s)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"BOOL" | "BOOLEAN" => {
|
||||
if let Ok(b) = row.try_get(i) {
|
||||
JsonValue::Bool(b)
|
||||
} else {
|
||||
let x: String = row.get(i);
|
||||
JsonValue::Bool(x.to_lowercase() == "true")
|
||||
}
|
||||
}
|
||||
"INT" | "NUMBER" | "INTEGER" | "BIGINT" | "INT8" => {
|
||||
if let Ok(n) = row.try_get::<i64, usize>(i) {
|
||||
JsonValue::Number(n.into())
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
"REAL" => {
|
||||
if let Ok(n) = row.try_get::<f64, usize>(i) {
|
||||
JsonValue::from(n)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
// "JSON" => JsonValue::Object(row.get(i)),
|
||||
"BLOB" => {
|
||||
if let Ok(n) = row.try_get::<Vec<u8>, usize>(i) {
|
||||
JsonValue::Array(
|
||||
n.into_iter().map(|n| JsonValue::Number(n.into())).collect(),
|
||||
)
|
||||
} else {
|
||||
JsonValue::Null
|
||||
}
|
||||
}
|
||||
_ => JsonValue::Null,
|
||||
}
|
||||
};
|
||||
value.insert(column.name().to_string(), v);
|
||||
}
|
||||
values.push(value);
|
||||
}
|
||||
Ok(values)
|
||||
}
|
||||
|
||||
/// Tauri SQL plugin.
|
||||
pub struct TauriSql<R: Runtime> {
|
||||
migrations: Option<HashMap<String, MigrationList>>,
|
||||
invoke_handler: Box<dyn Fn(Invoke<R>) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Default for TauriSql<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
migrations: Some(Default::default()),
|
||||
invoke_handler: Box::new(tauri::generate_handler![load, execute, select, close]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> TauriSql<R> {
|
||||
/// Add migrations to a database.
|
||||
#[must_use]
|
||||
pub fn add_migrations(mut self, db_url: &str, migrations: Vec<Migration>) -> Self {
|
||||
self.migrations
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(db_url.to_string(), MigrationList(migrations));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Plugin<R> for TauriSql<R> {
|
||||
fn name(&self) -> &'static str {
|
||||
"sql"
|
||||
}
|
||||
|
||||
fn initialize(&mut self, app: &AppHandle<R>, config: serde_json::Value) -> PluginResult<()> {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let config: PluginConfig = if config.is_null() {
|
||||
Default::default()
|
||||
} else {
|
||||
serde_json::from_value(config)?
|
||||
};
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
create_dir_all(app_path(app)).expect("problems creating App directory!");
|
||||
|
||||
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);
|
||||
#[cfg(not(feature = "sqlite"))]
|
||||
let fqdb = db.clone();
|
||||
|
||||
if !Db::database_exists(&fqdb).await.unwrap_or(false) {
|
||||
Db::create_database(&fqdb).await?;
|
||||
}
|
||||
let pool = Pool::connect(&fqdb).await?;
|
||||
|
||||
if let Some(migrations) = self.migrations.as_mut().unwrap().remove(&db) {
|
||||
let migrator = Migrator::new(migrations).await?;
|
||||
migrator.run(&pool).await?;
|
||||
}
|
||||
lock.insert(db, pool);
|
||||
}
|
||||
drop(lock);
|
||||
app.manage(instances);
|
||||
app.manage(Migrations(Mutex::new(self.migrations.take().unwrap())));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn extend_api(&mut self, message: Invoke<R>) {
|
||||
(self.invoke_handler)(message)
|
||||
}
|
||||
|
||||
fn on_event(&mut self, app: &AppHandle<R>, event: &RunEvent) {
|
||||
if let RunEvent::Exit = event {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let instances = &*app.state::<DbInstances>();
|
||||
let instances = instances.0.lock().await;
|
||||
for value in instances.values() {
|
||||
value.close().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user