Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1c1ac56
commit 2e53a80
Showing
3 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# Created by https://www.toptal.com/developers/gitignore/api/linux,macos,rust | ||
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,rust | ||
|
||
### Linux ### | ||
*~ | ||
|
||
# temporary files which can be created if a process still has a handle open of a deleted file | ||
.fuse_hidden* | ||
|
||
# KDE directory preferences | ||
.directory | ||
|
||
# Linux trash folder which might appear on any partition or disk | ||
.Trash-* | ||
|
||
# .nfs files are created when an open file is removed but is still being accessed | ||
.nfs* | ||
|
||
### macOS ### | ||
# General | ||
.DS_Store | ||
.AppleDouble | ||
.LSOverride | ||
|
||
# Icon must end with two \r | ||
Icon | ||
|
||
|
||
# Thumbnails | ||
._* | ||
|
||
# Files that might appear in the root of a volume | ||
.DocumentRevisions-V100 | ||
.fseventsd | ||
.Spotlight-V100 | ||
.TemporaryItems | ||
.Trashes | ||
.VolumeIcon.icns | ||
.com.apple.timemachine.donotpresent | ||
|
||
# Directories potentially created on remote AFP share | ||
.AppleDB | ||
.AppleDesktop | ||
Network Trash Folder | ||
Temporary Items | ||
.apdisk | ||
|
||
### macOS Patch ### | ||
# iCloud generated files | ||
*.icloud | ||
|
||
### Rust ### | ||
# Generated by Cargo | ||
# will have compiled files and executables | ||
debug/ | ||
target/ | ||
|
||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries | ||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html | ||
Cargo.lock | ||
|
||
# These are backup files generated by rustfmt | ||
**/*.rs.bk | ||
|
||
# MSVC Windows builds of rustc generate these, which store debugging information | ||
*.pdb | ||
|
||
# End of https://www.toptal.com/developers/gitignore/api/linux,macos,rust |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
[package] | ||
name = "rusty-sqlite3" | ||
description = "Experimental project for using SQLx with SQLite3 as an npm module." | ||
repository = "https://github.com/ming900518/rusty-sqlite3" | ||
version = "0.1.0" | ||
authors = ["Ming Chang <mail@mingchang.tw>"] | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib", "rlib"] | ||
|
||
[dependencies] | ||
wasm-bindgen = "*" | ||
wasm-bindgen-futures = "*" | ||
serde = { version = "*", features = ["derive"]} | ||
serde-wasm-bindgen = "*" | ||
time = { version = "*", features = ["serde-human-readable", "macros", "wasm-bindgen"]} | ||
tokio = { version = "*", features = ["macros", "rt"]} | ||
sqlx = { version = "*", features = ["runtime-tokio-rustls", "time", "sqlite"]} | ||
|
||
[profile.release] | ||
lto = true | ||
codegen-units = 1 | ||
panic = "abort" | ||
opt-level = "s" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// For better looking JS function. | ||
#![allow(non_snake_case)] | ||
use std::fmt::Debug; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
use serde_wasm_bindgen::{from_value, to_value}; | ||
use sqlx::{SqliteConnection, Connection, query, Row, Column}; | ||
use wasm_bindgen::{prelude::*, JsValue}; | ||
|
||
#[derive(Deserialize)] | ||
struct SqliteConnectionSetting { | ||
connection_method: SqliteConnectionMethod | ||
} | ||
|
||
#[derive(Deserialize)] | ||
enum SqliteConnectionMethod { | ||
Memory, | ||
File(SqliteFileConnection) | ||
} | ||
|
||
#[derive(Deserialize)] | ||
struct SqliteFileConnection { | ||
username: Option<String>, | ||
password: Option<String>, | ||
filepath: String | ||
} | ||
|
||
#[wasm_bindgen] | ||
pub async fn connect(connectInfo: JsValue) -> Result<*mut SqliteConnection, JsError> { | ||
let Ok(connection_setting) = deserialize_raw_data::<SqliteConnectionSetting>(connectInfo) else { | ||
return Err(JsError::new("Unable to deserialize connection setting.")) | ||
}; | ||
|
||
let connection = if let SqliteConnectionMethod::File(file_connection) = connection_setting.connection_method { | ||
match (file_connection.username, file_connection.password) { | ||
(Some(username), Some(password)) => { | ||
if let Ok(connect) = SqliteConnection::connect(&format!("sqlite://{}:{}@{}", username, password, file_connection.filepath)).await { | ||
connect | ||
} else { | ||
return Err(JsError::new("Unable to connect with username/password.")) | ||
} | ||
}, | ||
(None, None) => { | ||
if let Ok(connect) = SqliteConnection::connect(&format!("sqlite://{}", file_connection.filepath)).await { | ||
connect | ||
} else { | ||
return Err(JsError::new("Unable to connect without password.")) | ||
} | ||
}, | ||
_ => { | ||
return Err(JsError::new("Not enough parameter provided. Parameters required by authentication: username, password.")) | ||
} | ||
} | ||
} else if let Ok(connect) = SqliteConnection::connect("sqlite::memory:").await { | ||
connect | ||
} else { | ||
return Err(JsError::new("Unable to connect in memory database.")) | ||
}; | ||
Ok(Box::into_raw(Box::new(connection))) | ||
} | ||
|
||
#[wasm_bindgen(getter_with_clone)] | ||
pub struct QueryResult { | ||
pub result: JsValue, | ||
pub connection: *mut SqliteConnection | ||
} | ||
|
||
|
||
#[wasm_bindgen] | ||
pub async fn execute(connection: *mut SqliteConnection, sql: &str, parameters: JsValue) -> Result<QueryResult, JsError> { | ||
let mut db = unsafe { Box::from_raw(connection) }; | ||
let Ok(parameters) = deserialize_raw_data::<Vec<String>>(parameters) else { | ||
return Err(JsError::new("Unable to deserialize parameters.")) | ||
}; | ||
let mut query = query(sql); | ||
|
||
for i in 0..parameters.len() { | ||
query = query.bind(parameters.get(i).unwrap()); | ||
} | ||
|
||
match query.fetch_all(&mut *db).await { | ||
Ok(results) => { | ||
let result = results.into_iter().enumerate().map(|(index, row)| { | ||
let column = row.column(index).name().to_owned(); | ||
let field = row.get::<String, usize>(index); | ||
(column, field) | ||
}).collect::<Vec<(String, String)>>(); | ||
convert_to_js_value(db, result) | ||
}, | ||
Err(error) => Err(JsError::new(&format!("Unable to fetch data: {}", error))) | ||
} | ||
} | ||
|
||
fn deserialize_raw_data<T: for<'de> Deserialize<'de>>(js_data: JsValue) -> Result<T, JsError> { | ||
match from_value::<T>(js_data) { | ||
Ok(raw_result) => Ok(raw_result), | ||
Err(error) => Err(JsError::new(&format!( | ||
"Unable to deserialize the input. Detail: {}", | ||
error | ||
))), | ||
} | ||
} | ||
|
||
fn convert_to_js_value<T: Serialize + Debug>(db: Box<SqliteConnection>, data: T) -> Result<QueryResult, JsError> { | ||
match to_value(&data) { | ||
Ok(output) => { | ||
Ok(QueryResult {result: output, connection: Box::into_raw(db) }) | ||
}, | ||
Err(error) => Err(JsError::new(&format!("Error occured when serializing the result: {}.\n\nValue (force displayed with Debug trait): \n{:#?}", error, data))) | ||
} | ||
} |