diff --git a/diesel-wasm-sqlite/Cargo.lock b/diesel-wasm-sqlite/Cargo.lock index 7c3889c6e..8a32aca0c 100644 --- a/diesel-wasm-sqlite/Cargo.lock +++ b/diesel-wasm-sqlite/Cargo.lock @@ -75,9 +75,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" [[package]] name = "cfg-if" @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "diesel-async" version = "0.5.0" -source = "git+https://github.com/insipx/diesel_async?branch=insipx/wasm-async#c255cddfecec3597fb102cfa85494f6e23cb6b9b" +source = "git+https://github.com/insipx/diesel_async?branch=insipx/make-stmt-cache-public#2cd189fb9300dcaeb204d05526cc9b0e079096c2" dependencies = [ "async-trait", "diesel", @@ -379,9 +379,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.2" +version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] diff --git a/diesel-wasm-sqlite/Cargo.toml b/diesel-wasm-sqlite/Cargo.toml index d844be5c7..737e6d77d 100644 --- a/diesel-wasm-sqlite/Cargo.toml +++ b/diesel-wasm-sqlite/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] # diesel = { git = "https://github.com/xmtp/diesel", branch = "insipx/wasm-backend", default-features = false, features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes"] } diesel = "2.2" -diesel-async = { git = "https://github.com/insipx/diesel_async", branch = "insipx/wasm-async" } +diesel-async = { git = "https://github.com/insipx/diesel_async", branch = "insipx/make-stmt-cache-public", features = ["stmt-cache"] } wasm-bindgen = "0.2.92" wasm-bindgen-futures = "0.4.42" log = "0.4" diff --git a/diesel-wasm-sqlite/package.js b/diesel-wasm-sqlite/package.js index ca65d63e9..7c4e4b335 100644 --- a/diesel-wasm-sqlite/package.js +++ b/diesel-wasm-sqlite/package.js @@ -273,14 +273,23 @@ export class SQLite { } } - create_function(database, functionName, nArg, textRep, xFunc, xStep, xFinal) { + create_function( + database, + functionName, + nArg, + textRep, + pApp, + xFunc, + xStep, + xFinal, + ) { try { sqlite.create_function( database, functionName, nArg, textRep, - 0, + pApp, // pApp is ignored xFunc, xStep, xFinal, @@ -290,4 +299,10 @@ export class SQLite { console.log("create function err"); } } + + /* + serialize(database, zSchema, size, flags) { + return this.module._sqlite3_serialize(database, zSchema, size, flags); + } + */ } diff --git a/diesel-wasm-sqlite/src/connection/functions.rs b/diesel-wasm-sqlite/src/connection/functions.rs index db330ee23..b8d8f7538 100644 --- a/diesel-wasm-sqlite/src/connection/functions.rs +++ b/diesel-wasm-sqlite/src/connection/functions.rs @@ -1,22 +1,23 @@ -extern crate libsqlite3_sys as ffi; - use super::raw::RawConnection; use super::row::PrivateSqliteRow; -use super::{Sqlite, SqliteAggregateFunction, SqliteBindValue}; -use crate::backend::Backend; -use crate::deserialize::{FromSqlRow, StaticallySizedRow}; -use crate::result::{DatabaseErrorKind, Error, QueryResult}; -use crate::row::{Field, PartialRow, Row, RowIndex, RowSealed}; -use crate::serialize::{IsNull, Output, ToSql}; -use crate::sql_types::HasSqlType; -use crate::sqlite::connection::bind_collector::InternalSqliteBindValue; -use crate::sqlite::connection::sqlite_value::OwnedSqliteValue; -use crate::sqlite::SqliteValue; +use super::{/*SqliteAggregateFunction,*/ SqliteBindValue, WasmSqlite}; +use crate::connection::bind_collector::InternalSqliteBindValue; +use crate::connection::sqlite_value::OwnedSqliteValue; +use crate::connection::SqliteValue; +use diesel::backend::Backend; +use diesel::deserialize::{FromSqlRow, StaticallySizedRow}; +use diesel::result::{DatabaseErrorKind, Error, QueryResult}; +use diesel::row::{Field, PartialRow, Row, RowIndex, RowSealed}; +use diesel::serialize::{IsNull, Output, ToSql}; +use diesel::sql_types::HasSqlType; +use futures::future::BoxFuture; +use futures::FutureExt; use std::cell::{Ref, RefCell}; use std::marker::PhantomData; use std::mem::ManuallyDrop; use std::ops::DerefMut; use std::rc::Rc; +use wasm_bindgen::JsValue; pub(super) fn register( conn: &RawConnection, @@ -25,10 +26,10 @@ pub(super) fn register( mut f: F, ) -> QueryResult<()> where - F: FnMut(&RawConnection, Args) -> Ret + std::panic::UnwindSafe + Send + 'static, - Args: FromSqlRow + StaticallySizedRow, - Ret: ToSql, - Sqlite: HasSqlType, + F: FnMut(&RawConnection, Args) -> BoxFuture<'static, Ret>, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + WasmSqlite: HasSqlType, { let fields_needed = Args::FIELD_COUNT; if fields_needed > 127 { @@ -39,13 +40,19 @@ where } conn.register_sql_function(fn_name, fields_needed, deterministic, move |conn, args| { - let args = build_sql_function_args::(args)?; - - Ok(f(conn, args)) + async { + let args = build_sql_function_args::(args)?; + let conn = RawConnection { + internal_connection: conn, + }; + Ok(f(&conn, args).await) + } + .boxed() })?; Ok(()) } +/* pub(super) fn register_noargs( conn: &RawConnection, fn_name: &str, @@ -54,8 +61,8 @@ pub(super) fn register_noargs( ) -> QueryResult<()> where F: FnMut() -> Ret + std::panic::UnwindSafe + Send + 'static, - Ret: ToSql, - Sqlite: HasSqlType, + Ret: ToSql, + WasmSqlite: HasSqlType, { conn.register_sql_function(fn_name, 0, deterministic, move |_, _| Ok(f()))?; Ok(()) @@ -67,9 +74,9 @@ pub(super) fn register_aggregate( ) -> QueryResult<()> where A: SqliteAggregateFunction + 'static + Send + std::panic::UnwindSafe, - Args: FromSqlRow + StaticallySizedRow, - Ret: ToSql, - Sqlite: HasSqlType, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + WasmSqlite: HasSqlType, { let fields_needed = Args::FIELD_COUNT; if fields_needed > 127 { @@ -86,12 +93,11 @@ where Ok(()) } +*/ -pub(super) fn build_sql_function_args( - args: &mut [*mut ffi::sqlite3_value], -) -> Result +pub(super) fn build_sql_function_args(args: Vec) -> Result where - Args: FromSqlRow, + Args: FromSqlRow, { let row = FunctionRow::new(args); Args::build_from_row(&row).map_err(Error::DeserializationError) @@ -104,8 +110,8 @@ pub(super) fn process_sql_function_result( result: &'_ Ret, ) -> QueryResult> where - Ret: ToSql, - Sqlite: HasSqlType, + Ret: ToSql, + WasmSqlite: HasSqlType, { let mut metadata_lookup = (); let value = SqliteBindValue { @@ -126,7 +132,7 @@ struct FunctionRow<'a> { // as this buffer is owned by sqlite not by diesel args: Rc>>>, field_count: usize, - marker: PhantomData<&'a ffi::sqlite3_value>, + marker: PhantomData<&'a JsValue>, } impl<'a> Drop for FunctionRow<'a> { @@ -148,37 +154,17 @@ impl<'a> Drop for FunctionRow<'a> { impl<'a> FunctionRow<'a> { #[allow(unsafe_code)] // complicated ptr cast - fn new(args: &mut [*mut ffi::sqlite3_value]) -> Self { + fn new(args: Vec) -> Self { let lengths = args.len(); - let args = unsafe { - Vec::from_raw_parts( - // This cast is safe because: - // * Casting from a pointer to an array to a pointer to the first array - // element is safe - // * Casting from a raw pointer to `NonNull` is safe, - // because `NonNull` is #[repr(transparent)] - // * Casting from `NonNull` to `OwnedSqliteValue` is safe, - // as the struct is `#[repr(transparent)] - // * Casting from `NonNull` to `Option>` as the documentation - // states: "This is so that enums may use this forbidden value as a discriminant – - // Option> has the same size as *mut T" - // * The last point remains true for `OwnedSqliteValue` as `#[repr(transparent)] - // guarantees the same layout as the inner type - // * It's unsafe to drop the vector (and the vector elements) - // because of this we wrap the vector (or better the Row) - // Into `ManualDrop` to prevent the dropping - args as *mut [*mut ffi::sqlite3_value] as *mut ffi::sqlite3_value - as *mut Option, - lengths, - lengths, - ) - }; Self { field_count: lengths, args: Rc::new(RefCell::new(ManuallyDrop::new( PrivateSqliteRow::Duplicated { - values: args, + values: args + .into_iter() + .map(|a| Some(OwnedSqliteValue { value: a.into() })) + .collect(), column_names: Rc::from(vec![None; lengths]), }, ))), @@ -189,7 +175,7 @@ impl<'a> FunctionRow<'a> { impl RowSealed for FunctionRow<'_> {} -impl<'a> Row<'a, Sqlite> for FunctionRow<'a> { +impl<'a> Row<'a, WasmSqlite> for FunctionRow<'a> { type Field<'f> = FunctionArgument<'f> where 'a: 'f, Self: 'f; type InnerPartialRow = Self; @@ -200,7 +186,7 @@ impl<'a> Row<'a, Sqlite> for FunctionRow<'a> { fn get<'b, I>(&'b self, idx: I) -> Option> where 'a: 'b, - Self: crate::row::RowIndex, + Self: RowIndex, { let idx = self.idx(idx)?; Some(FunctionArgument { @@ -235,7 +221,7 @@ struct FunctionArgument<'a> { col_idx: i32, } -impl<'a> Field<'a, Sqlite> for FunctionArgument<'a> { +impl<'a> Field<'a, WasmSqlite> for FunctionArgument<'a> { fn field_name(&self) -> Option<&str> { None } @@ -244,7 +230,7 @@ impl<'a> Field<'a, Sqlite> for FunctionArgument<'a> { self.value().is_none() } - fn value(&self) -> Option<::RawValue<'_>> { + fn value(&self) -> Option<::RawValue<'_>> { SqliteValue::new( Ref::map(Ref::clone(&self.args), |drop| std::ops::Deref::deref(drop)), self.col_idx, diff --git a/diesel-wasm-sqlite/src/connection/mod.rs b/diesel-wasm-sqlite/src/connection/mod.rs index a4029d1a2..0fb898c86 100644 --- a/diesel-wasm-sqlite/src/connection/mod.rs +++ b/diesel-wasm-sqlite/src/connection/mod.rs @@ -12,47 +12,62 @@ pub(crate) use self::bind_collector::SqliteBindCollector; pub use self::bind_collector::SqliteBindValue; pub use self::sqlite_value::SqliteValue; // pub use self::serialized_database::SerializedDatabase; - -/* + use self::raw::RawConnection; -use self::statement_iterator::*; +// use self::statement_iterator::*; use self::stmt::{Statement, StatementUse}; -use super::SqliteAggregateFunction; -use crate::connection::instrumentation::StrQueryHelper; -use crate::connection::statement_cache::StatementCache; use crate::query_builder::*; +use diesel::connection::{DefaultLoadingMode, LoadConnection}; use diesel::deserialize::{FromSqlRow, StaticallySizedRow}; use diesel::expression::QueryMetadata; use diesel::result::*; use diesel::serialize::ToSql; -use diesel::sql_types::{HasSqlType, TypeMetadata}; -*/ +use diesel::sql_types::HasSqlType; +use futures::{FutureExt, TryFutureExt}; -use diesel::{connection::Instrumentation, query_builder::{AsQuery, QueryFragment, QueryId}, QueryResult}; -pub use diesel_async::{AnsiTransactionManager, AsyncConnection, SimpleAsyncConnection}; +use diesel::{connection::{ConnectionSealed, Instrumentation, WithMetadataLookup}, query_builder::{AsQuery, QueryFragment, QueryId}, sql_types::TypeMetadata, QueryResult}; +pub use diesel_async::{AnsiTransactionManager, AsyncConnection, SimpleAsyncConnection, TransactionManager, stmt_cache::StmtCache}; use futures::{future::BoxFuture, stream::BoxStream}; use row::SqliteRow; use crate::{get_sqlite_unchecked, WasmSqlite, WasmSqliteError}; -unsafe impl Send for WasmSqliteConnection {} -#[derive(Debug)] pub struct WasmSqliteConnection { - pub raw: raw::RawConnection, + // statement_cache needs to be before raw_connection + // otherwise we will get errors about open statements before closing the + // connection itself + statement_cache: StmtCache, + raw_connection: RawConnection, + transaction_state: AnsiTransactionManager, + // this exists for the sole purpose of implementing `WithMetadataLookup` trait + // and avoiding static mut which will be deprecated in 2024 edition + metadata_lookup: (), + instrumentation: Option>, } -#[async_trait::async_trait(?Send)] + +// This relies on the invariant that RawConnection or Statement are never +// leaked. If a reference to one of those was held on a different thread, this +// would not be thread safe. +// Web is in one thread. Web workers can be used to hold a WasmSqliteConnection +// separately. + +#[allow(unsafe_code)] +unsafe impl Send for WasmSqliteConnection {} + + +impl ConnectionSealed for WasmSqliteConnection {} + + #[async_trait::async_trait(?Send)] impl SimpleAsyncConnection for WasmSqliteConnection { async fn batch_execute(&mut self, query: &str) -> diesel::prelude::QueryResult<()> { get_sqlite_unchecked() - .batch_execute(&self.raw.internal_connection, query) + .batch_execute(&self.raw_connection.internal_connection, query) .map_err(WasmSqliteError::from) .map_err(Into::into) } } -impl diesel::connection::ConnectionSealed for WasmSqliteConnection {} - #[async_trait::async_trait(?Send)] impl AsyncConnection for WasmSqliteConnection { type Backend = WasmSqlite; @@ -64,7 +79,11 @@ impl AsyncConnection for WasmSqliteConnection { async fn establish(database_url: &str) -> diesel::prelude::ConnectionResult { Ok(WasmSqliteConnection { - raw: raw::RawConnection::establish(database_url).await.unwrap(), + statement_cache: StmtCache::new(), + raw_connection: raw::RawConnection::establish(database_url).await.unwrap(), + transaction_state: Default::default(), + metadata_lookup: (), + instrumentation: None }) } @@ -102,99 +121,7 @@ impl AsyncConnection for WasmSqliteConnection { } /* -pub struct SqliteConnection { - // statement_cache needs to be before raw_connection - // otherwise we will get errors about open statements before closing the - // connection itself - statement_cache: StatementCache, - raw_connection: RawConnection, - transaction_state: AnsiTransactionManager, - // this exists for the sole purpose of implementing `WithMetadataLookup` trait - // and avoiding static mut which will be deprecated in 2024 edition - metadata_lookup: (), - instrumentation: Option>, -} -*/ - -// This relies on the invariant that RawConnection or Statement are never -// leaked. If a reference to one of those was held on a different thread, this -// would not be thread safe. -/* -#[allow(unsafe_code)] -unsafe impl Send for SqliteConnection {} - -impl SimpleConnection for SqliteConnection { - fn batch_execute(&mut self, query: &str) -> QueryResult<()> { - self.instrumentation - .on_connection_event(InstrumentationEvent::StartQuery { - query: &StrQueryHelper::new(query), - }); - let resp = self.raw_connection.exec(query); - self.instrumentation - .on_connection_event(InstrumentationEvent::FinishQuery { - query: &StrQueryHelper::new(query), - error: resp.as_ref().err(), - }); - resp - } -} -*/ -/* -impl ConnectionSealed for SqliteConnection {} - -impl Connection for SqliteConnection { - type Backend = Sqlite; - type TransactionManager = AnsiTransactionManager; - - /// Establish a connection to the database specified by `database_url`. - /// - /// See [SqliteConnection] for supported `database_url`. - /// - /// If the database does not exist, this method will try to - /// create a new database and then establish a connection to it. - fn establish(database_url: &str) -> ConnectionResult { - let mut instrumentation = crate::connection::instrumentation::get_default_instrumentation(); - instrumentation.on_connection_event(InstrumentationEvent::StartEstablishConnection { - url: database_url, - }); - - let establish_result = Self::establish_inner(database_url); - instrumentation.on_connection_event(InstrumentationEvent::FinishEstablishConnection { - url: database_url, - error: establish_result.as_ref().err(), - }); - let mut conn = establish_result?; - conn.instrumentation = instrumentation; - Ok(conn) - } - - fn execute_returning_count(&mut self, source: &T) -> QueryResult - where - T: QueryFragment + QueryId, - { - let statement_use = self.prepared_query(source)?; - statement_use - .run() - .map(|_| self.raw_connection.rows_affected_by_last_query()) - } - - fn transaction_state(&mut self) -> &mut AnsiTransactionManager - where - Self: Sized, - { - &mut self.transaction_state - } - - fn instrumentation(&mut self) -> &mut dyn Instrumentation { - &mut self.instrumentation - } - - fn set_instrumentation(&mut self, instrumentation: impl Instrumentation) { - self.instrumentation = Some(Box::new(instrumentation)); - } -} - -impl LoadConnection for SqliteConnection { +impl LoadConnection for WasmSqliteConnection { type Cursor<'conn, 'query> = StatementIterator<'conn, 'query>; type Row<'conn, 'query> = self::row::SqliteRow<'conn, 'query>; @@ -211,12 +138,14 @@ impl LoadConnection for SqliteConnection { Ok(StatementIterator::new(statement)) } } - -impl WithMetadataLookup for SqliteConnection { - fn metadata_lookup(&mut self) -> &mut ::MetadataLookup { +*/ +/* +impl WithMetadataLookup for WasmSqliteConnection { + fn metadata_lookup(&mut self) -> &mut ::MetadataLookup { &mut self.metadata_lookup } } + */ #[cfg(feature = "r2d2")] impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection { @@ -230,7 +159,7 @@ impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection { AnsiTransactionManager::is_broken_transaction_manager(self) } } - + /* impl MultiConnectionHelper for SqliteConnection { fn to_any<'a>( lookup: &mut ::MetadataLookup, @@ -244,8 +173,9 @@ impl MultiConnectionHelper for SqliteConnection { lookup.downcast_mut() } } +*/ -impl SqliteConnection { +impl WasmSqliteConnection { /// Run a transaction with `BEGIN IMMEDIATE` /// /// This method will return an error if a transaction is already open. @@ -267,12 +197,12 @@ impl SqliteConnection { /// }) /// # } /// ``` - pub fn immediate_transaction(&mut self, f: F) -> Result + pub async fn immediate_transaction(&mut self, f: F) -> Result where F: FnOnce(&mut Self) -> Result, E: From, { - self.transaction_sql(f, "BEGIN IMMEDIATE") + self.transaction_sql(f, "BEGIN IMMEDIATE").await } /// Run a transaction with `BEGIN EXCLUSIVE` @@ -296,67 +226,58 @@ impl SqliteConnection { /// }) /// # } /// ``` - pub fn exclusive_transaction(&mut self, f: F) -> Result + pub async fn exclusive_transaction(&mut self, f: F) -> Result where F: FnOnce(&mut Self) -> Result, E: From, { - self.transaction_sql(f, "BEGIN EXCLUSIVE") + self.transaction_sql(f, "BEGIN EXCLUSIVE").await } - fn transaction_sql(&mut self, f: F, sql: &str) -> Result + async fn transaction_sql(&mut self, f: F, sql: &str) -> Result where F: FnOnce(&mut Self) -> Result, E: From, { - AnsiTransactionManager::begin_transaction_sql(&mut *self, sql)?; + AnsiTransactionManager::begin_transaction_sql(&mut *self, sql).await?; match f(&mut *self) { Ok(value) => { - AnsiTransactionManager::commit_transaction(&mut *self)?; + AnsiTransactionManager::commit_transaction(&mut *self).await?; Ok(value) } Err(e) => { - AnsiTransactionManager::rollback_transaction(&mut *self)?; + AnsiTransactionManager::rollback_transaction(&mut *self).await?; Err(e) } } } - +/* fn prepared_query<'conn, 'query, T>( &'conn mut self, source: T, ) -> QueryResult> where - T: QueryFragment + QueryId + 'query, + T: QueryFragment + QueryId + 'query, { - self.instrumentation - .on_connection_event(InstrumentationEvent::StartQuery { - query: &crate::debug_query(&source), - }); let raw_connection = &self.raw_connection; let cache = &mut self.statement_cache; - let statement = match cache.cached_statement( + let statement = match cache.cached_prepared_statement( &source, - &Sqlite, + &WasmSqlite, &[], |sql, is_cached| Statement::prepare(raw_connection, sql, is_cached), &mut self.instrumentation, ) { Ok(statement) => statement, Err(e) => { - self.instrumentation - .on_connection_event(InstrumentationEvent::FinishQuery { - query: &crate::debug_query(&source), - error: Some(&e), - }); - return Err(e); } }; StatementUse::bind(statement, source, &mut self.instrumentation) } - + */ + /* #[doc(hidden)] pub fn register_sql_function( &mut self, @@ -366,9 +287,9 @@ impl SqliteConnection { ) -> QueryResult<()> where F: FnMut(Args) -> Ret + std::panic::UnwindSafe + Send + 'static, - Args: FromSqlRow + StaticallySizedRow, - Ret: ToSql, - Sqlite: HasSqlType, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + WasmSqlite: HasSqlType, { functions::register( &self.raw_connection, @@ -377,7 +298,7 @@ impl SqliteConnection { move |_, args| f(args), ) } - + #[doc(hidden)] pub fn register_noarg_sql_function( &self, @@ -387,8 +308,8 @@ impl SqliteConnection { ) -> QueryResult<()> where F: FnMut() -> Ret + std::panic::UnwindSafe + Send + 'static, - Ret: ToSql, - Sqlite: HasSqlType, + Ret: ToSql, + WasmSqlite: HasSqlType, { functions::register_noargs(&self.raw_connection, fn_name, deterministic, f) } @@ -406,7 +327,7 @@ impl SqliteConnection { { functions::register_aggregate::<_, _, _, _, A>(&self.raw_connection, fn_name) } - + /// Register a collation function. /// /// `collation` must always return the same answer given the same inputs. @@ -449,7 +370,6 @@ impl SqliteConnection { self.raw_connection .register_collation_function(collation_name, collation) } - /// Serialize the current SQLite database into a byte buffer. /// /// The serialized data is identical to the data that would be written to disk if the database @@ -461,7 +381,7 @@ impl SqliteConnection { pub fn serialize_database_to_buffer(&mut self) -> SerializedDatabase { self.raw_connection.serialize() } - + /// Deserialize an SQLite database from a byte buffer. /// /// This function takes a byte slice and attempts to deserialize it into a SQLite database. @@ -501,45 +421,49 @@ impl SqliteConnection { pub fn deserialize_readonly_database_from_buffer(&mut self, data: &[u8]) -> QueryResult<()> { self.raw_connection.deserialize(data) } - - fn register_diesel_sql_functions(&self) -> QueryResult<()> { - use crate::sql_types::{Integer, Text}; + */ + /* + async fn register_diesel_sql_functions(&self) -> QueryResult<()> { + use diesel::sql_types::{Integer, Text}; functions::register::( &self.raw_connection, "diesel_manage_updated_at", false, - |conn, table_name: String| { + |conn, table_name: String| async { conn.exec(&format!( include_str!("diesel_manage_updated_at.sql"), table_name = table_name )) + .await .expect("Failed to create trigger"); 0 // have to return *something* - }, + }.boxed(), ) } + */ - fn establish_inner(database_url: &str) -> Result { - use crate::result::ConnectionError::CouldntSetupConfiguration; - let raw_connection = RawConnection::establish(database_url)?; + async fn establish_inner(database_url: &str) -> Result { + use diesel::result::ConnectionError::CouldntSetupConfiguration; + let raw_connection = RawConnection::establish(database_url).await.unwrap(); let conn = Self { - statement_cache: StatementCache::new(), + statement_cache: StmtCache::new(), raw_connection, transaction_state: AnsiTransactionManager::default(), metadata_lookup: (), instrumentation: None, }; + /* conn.register_diesel_sql_functions() - .map_err(CouldntSetupConfiguration)?; + .map_err(CouldntSetupConfiguration).await?; + */ Ok(conn) } } -*/ /* fn error_message(err_code: i32) -> &'static str { - let sqlite3 = crate::get_sqlite_unchecked(); + let sqlite3 = diesel::get_sqlite_unchecked(); sqlite3.code_to_str(err_code) } */ diff --git a/diesel-wasm-sqlite/src/connection/raw.rs b/diesel-wasm-sqlite/src/connection/raw.rs index cd971af49..c7874c9f6 100644 --- a/diesel-wasm-sqlite/src/connection/raw.rs +++ b/diesel-wasm-sqlite/src/connection/raw.rs @@ -12,9 +12,8 @@ use crate::{ sqlite_types::{SqliteFlags, SqliteOpenFlags}, WasmSqlite, WasmSqliteError, }; -use diesel::result::*; -use diesel::serialize::ToSql; -use diesel::sql_types::HasSqlType; +use diesel::{result::*, serialize::ToSql, sql_types::HasSqlType}; +use futures::future::BoxFuture; use wasm_bindgen::{closure::Closure, JsValue}; /* /// For use in FFI function, which cannot unwind. @@ -73,15 +72,17 @@ impl RawConnection { sqlite3.changes(&self.internal_connection) } - pub(super) fn register_sql_function( + pub(super) fn register_sql_function( &self, fn_name: &str, - num_args: i32, + num_args: usize, deterministic: bool, f: F, ) -> QueryResult<()> where - F: FnMut(JsValue, JsValue) + 'static, + F: FnMut(JsValue, Vec) -> JsValue + 'static, + Ret: ToSql, + WasmSqlite: HasSqlType, { let sqlite3 = crate::get_sqlite_unchecked(); let flags = Self::get_flags(deterministic); @@ -91,8 +92,11 @@ impl RawConnection { .create_function( &self.internal_connection, fn_name, - num_args, + num_args + .try_into() + .expect("usize to i32 panicked in register_sql_function"), flags, + 0, Some(&cb), None, None, @@ -108,6 +112,38 @@ impl RawConnection { } flags.bits() as i32 } + + /* possible to implement this, but would need to fill in the missing wa-sqlite functions + pub(super) fn serialize(&mut self) -> SerializedDatabase { + unsafe { + let mut size: ffi::sqlite3_int64 = 0; + let data_ptr = ffi::sqlite3_serialize( + self.internal_connection.as_ptr(), + std::ptr::null(), + &mut size as *mut _, + 0, + ); + SerializedDatabase::new(data_ptr, size as usize) + } + } + + pub(super) fn deserialize(&mut self, data: &[u8]) -> QueryResult<()> { + // the cast for `ffi::SQLITE_DESERIALIZE_READONLY` is required for old libsqlite3-sys versions + #[allow(clippy::unnecessary_cast)] + unsafe { + let result = ffi::sqlite3_deserialize( + self.internal_connection.as_ptr(), + std::ptr::null(), + data.as_ptr() as *mut u8, + data.len() as i64, + data.len() as i64, + ffi::SQLITE_DESERIALIZE_READONLY as u32, + ); + + ensure_sqlite_ok(result, self.internal_connection.as_ptr()) + } + } + */ } /* TODO: AsyncDrop diff --git a/diesel-wasm-sqlite/src/connection/serialized_database.rs b/diesel-wasm-sqlite/src/connection/serialized_database.rs index 28a98a286..e302a097c 100644 --- a/diesel-wasm-sqlite/src/connection/serialized_database.rs +++ b/diesel-wasm-sqlite/src/connection/serialized_database.rs @@ -1,13 +1,10 @@ -#![allow(unsafe_code)] -extern crate libsqlite3_sys as ffi; - use std::ops::Deref; /// `SerializedDatabase` is a wrapper for a serialized database that is dynamically allocated by calling `sqlite3_serialize`. /// This RAII wrapper is necessary to deallocate the memory when it goes out of scope with `sqlite3_free`. #[derive(Debug)] pub struct SerializedDatabase { - data: *mut u8, + data: JsValue, len: usize, } diff --git a/diesel-wasm-sqlite/src/connection/stmt.rs b/diesel-wasm-sqlite/src/connection/stmt.rs index 997994d95..a7adc6320 100644 --- a/diesel-wasm-sqlite/src/connection/stmt.rs +++ b/diesel-wasm-sqlite/src/connection/stmt.rs @@ -16,11 +16,13 @@ use diesel::{ result::{Error::DatabaseError, *}, }; use std::cell::OnceCell; +use std::sync::Arc; use wasm_bindgen::JsValue; pub(super) struct Statement { inner_statement: JsValue, + // drop_signal: Option>>, } impl Statement { @@ -49,9 +51,28 @@ impl Statement { ) .await .unwrap(); + /* + let (tx, rx) = tokio::sync::oneshot::channel::>(); + // We don't have `AsyncDrop` in rust yet. + // instead, we `send` a signal on Drop for the destructor to run in an + // asynchronously-spawned task. + + tokio::spawn(async move { + match rx.await { + Ok(this) => { + this.reset().await; + this.clear_bindings(); + } + Err(_) => { + log::error!("Statement never dropped"); + } + } + }); + */ Ok(Statement { inner_statement: stmt, + // drop_signal: Some(tx), }) } @@ -214,7 +235,7 @@ impl<'stmt, 'query> BoundStatement<'stmt, 'query> { // we don't track binds to free like sqlite3 C bindings // The assumption is that wa-sqlite, being WASM run in web browser that // lies in the middle of rust -> sqlite, takes care of this for us. - // if we run into memory issues, especailly memory leaks + // if we run into memory issues, especially memory leaks // this should be the first place to pay attention to. // // The bindings shuold be collected/freed with JS once `clear_bindings` is @@ -238,20 +259,33 @@ impl<'stmt, 'query> BoundStatement<'stmt, 'query> { } */ } + + // FIXME: [`AsyncDrop`](https://github.com/rust-lang/rust/issues/126482) is a missing feature in rust. + // Until then we need to manually reset the statement object. + pub async fn reset(&mut self) { + self.statement.reset().await; + self.statement.clear_bindings() + } } -// TODO: AsyncDrop +// Eventually replace with `AsyncDrop`: https://github.com/rust-lang/rust/issues/126482 impl<'stmt, 'query> Drop for BoundStatement<'stmt, 'query> { fn drop(&mut self) { + /* + let sender = self + .statement + .drop_signal + .take() + .expect("Drop may only be ran once"); + */ + // sender.send(Arc::new(*(&mut self.statement)); + /* // First reset the statement, otherwise the bind calls // below will fail self.statement.reset(); self.statement.clear_bindings(); - - if let Some(query) = &mut self.query { - std::mem::drop(query); - self.query = None; - } + */ + self.query.take(); } } diff --git a/diesel-wasm-sqlite/src/ffi.rs b/diesel-wasm-sqlite/src/ffi.rs index 6d011e95c..6b2355dea 100644 --- a/diesel-wasm-sqlite/src/ffi.rs +++ b/diesel-wasm-sqlite/src/ffi.rs @@ -203,8 +203,9 @@ extern "C" { functionName: &str, n_arg: i32, textRep: i32, - x_func: Option<&Closure>, - x_step: Option<&Closure>, + pApp: i32, //ignored + x_func: Option<&Closure) -> JsValue>>, + x_step: Option<&Closure) -> JsValue>>, x_final: Option<&Closure>, ) -> Result<(), JsValue>; } diff --git a/diesel-wasm-sqlite/src/lib.rs b/diesel-wasm-sqlite/src/lib.rs old mode 100644 new mode 100755 diff --git a/diesel-wasm-sqlite/src/wa-sqlite-diesel-bundle.js b/diesel-wasm-sqlite/src/wa-sqlite-diesel-bundle.js index 2a671e661..9e68f87cd 100644 --- a/diesel-wasm-sqlite/src/wa-sqlite-diesel-bundle.js +++ b/diesel-wasm-sqlite/src/wa-sqlite-diesel-bundle.js @@ -2972,14 +2972,23 @@ class SQLite { } } - create_function(database, functionName, nArg, textRep, xFunc, xStep, xFinal) { + create_function( + database, + functionName, + nArg, + textRep, + pApp, + xFunc, + xStep, + xFinal, + ) { try { sqlite.create_function( database, functionName, nArg, textRep, - 0, + pApp, // pApp is ignored xFunc, xStep, xFinal, @@ -2989,6 +2998,12 @@ class SQLite { console.log("create function err"); } } + + /* + serialize(database, zSchema, size, flags) { + return this.module._sqlite3_serialize(database, zSchema, size, flags); + } + */ } export { SQLite };