From d145da62de0243f6fea62082977b032ce32a9715 Mon Sep 17 00:00:00 2001 From: Andrew Plaza Date: Wed, 24 Jul 2024 21:49:05 -0400 Subject: [PATCH] add connection files, compile a few fns in raw connection --- Cargo.lock | 81 ++- diesel-wasm-sqlite/Cargo.toml | 7 +- diesel-wasm-sqlite/build.rs | 17 +- diesel-wasm-sqlite/package.js | 107 +-- diesel-wasm-sqlite/src/connection.rs | 2 - .../src/connection/bind_collector.rs | 22 +- .../connection/diesel_manage_updated_at.sql | 11 + .../src/connection/functions.rs | 253 +++++++ diesel-wasm-sqlite/src/connection/mod.rs | 636 ++++++++++++++++++ .../src/connection/owned_row.rs | 92 +++ diesel-wasm-sqlite/src/connection/raw.rs | 606 +++++++++++++++++ diesel-wasm-sqlite/src/connection/row.rs | 408 +++++++++++ .../src/connection/serialized_database.rs | 45 ++ .../src/connection/sqlite_value.rs | 172 +++++ .../src/connection/statement_iterator.rs | 172 +++++ diesel-wasm-sqlite/src/connection/stmt.rs | 540 +++++++++++++++ diesel-wasm-sqlite/src/ffi.rs | 63 +- diesel-wasm-sqlite/src/lib.rs | 111 +-- diesel-wasm-sqlite/src/package.js | 602 +++++++++++++++-- .../src/query_builder/returning.rs | 4 +- diesel-wasm-sqlite/src/sqlite_types.rs | 28 + diesel-wasm-sqlite/tests/web.rs | 2 +- 22 files changed, 3765 insertions(+), 216 deletions(-) delete mode 100644 diesel-wasm-sqlite/src/connection.rs create mode 100644 diesel-wasm-sqlite/src/connection/diesel_manage_updated_at.sql create mode 100644 diesel-wasm-sqlite/src/connection/functions.rs create mode 100644 diesel-wasm-sqlite/src/connection/mod.rs create mode 100644 diesel-wasm-sqlite/src/connection/owned_row.rs create mode 100644 diesel-wasm-sqlite/src/connection/raw.rs create mode 100644 diesel-wasm-sqlite/src/connection/row.rs create mode 100644 diesel-wasm-sqlite/src/connection/serialized_database.rs create mode 100644 diesel-wasm-sqlite/src/connection/sqlite_value.rs create mode 100644 diesel-wasm-sqlite/src/connection/statement_iterator.rs create mode 100644 diesel-wasm-sqlite/src/connection/stmt.rs diff --git a/Cargo.lock b/Cargo.lock index 0c61c6615..c29df6d2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,18 +1066,41 @@ name = "diesel" version = "2.2.0" source = "git+https://github.com/xmtp/diesel?branch=insipx/wasm-backend#a3fa32d4291bbae566cf544ca63a1aa2543da943" dependencies = [ - "diesel_derives", + "diesel_derives 2.2.0", "libsqlite3-sys", "r2d2", "time", ] +[[package]] +name = "diesel" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94" +dependencies = [ + "diesel_derives 2.2.2", +] + +[[package]] +name = "diesel-async" +version = "0.5.0" +source = "git+https://github.com/insipx/diesel_async?branch=insipx/wasm-async#c255cddfecec3597fb102cfa85494f6e23cb6b9b" +dependencies = [ + "async-trait", + "diesel 2.2.2", + "futures-util", + "scoped-futures", +] + [[package]] name = "diesel-wasm-sqlite" version = "0.1.1" dependencies = [ + "async-trait", + "bitflags 2.6.0", "console_error_panic_hook", - "diesel", + "diesel 2.2.2", + "diesel-async", "futures", "getrandom", "log", @@ -1094,8 +1117,21 @@ name = "diesel_derives" version = "2.2.0" source = "git+https://github.com/xmtp/diesel?branch=insipx/wasm-backend#a3fa32d4291bbae566cf544ca63a1aa2543da943" dependencies = [ - "diesel_table_macro_syntax", - "dsl_auto_type", + "diesel_table_macro_syntax 0.2.0 (git+https://github.com/xmtp/diesel?branch=insipx/wasm-backend)", + "dsl_auto_type 0.1.0", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "diesel_derives" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ff2be1e7312c858b2ef974f5c7089833ae57b5311b334b30923af58e5718d8" +dependencies = [ + "diesel_table_macro_syntax 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dsl_auto_type 0.1.2", "proc-macro2", "quote", "syn 2.0.71", @@ -1106,11 +1142,20 @@ name = "diesel_migrations" version = "2.2.0" source = "git+https://github.com/xmtp/diesel?branch=insipx/wasm-backend#a3fa32d4291bbae566cf544ca63a1aa2543da943" dependencies = [ - "diesel", + "diesel 2.2.0", "migrations_internals", "migrations_macros", ] +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn 2.0.71", +] + [[package]] name = "diesel_table_macro_syntax" version = "0.2.0" @@ -1207,6 +1252,20 @@ dependencies = [ "syn 2.0.71", ] +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling", + "either", + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "dunce" version = "1.0.4" @@ -4676,6 +4735,16 @@ dependencies = [ "parking_lot 0.12.3", ] +[[package]] +name = "scoped-futures" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" +dependencies = [ + "cfg-if", + "pin-utils", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -6584,7 +6653,7 @@ dependencies = [ "chrono", "criterion", "ctor", - "diesel", + "diesel 2.2.0", "diesel_migrations", "ed25519-dalek", "ethers", diff --git a/diesel-wasm-sqlite/Cargo.toml b/diesel-wasm-sqlite/Cargo.toml index 2afb4e20d..ae566a745 100644 --- a/diesel-wasm-sqlite/Cargo.toml +++ b/diesel-wasm-sqlite/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.1" edition = "2021" [dependencies] -diesel = { git = "https://github.com/xmtp/diesel", branch = "insipx/wasm-backend", features = ["r2d2", "i-implement-a-third-party-backend-and-opt-into-breaking-changes"] } +# 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" } wasm-bindgen = "0.2.92" wasm-bindgen-futures = "0.4.42" log = "0.4" @@ -15,6 +17,8 @@ web-sys = { version = "0.3", features = ["console"] } console_error_panic_hook = { version = "0.1", optional = true } tokio = { version = "1.38", default-features = false, features = ["rt", "macros", "sync", "io-util", "time"] } futures = "0.3" +async-trait = "0.1" +bitflags = "2.6" [dev-dependencies] rand = "0.8" @@ -28,3 +32,4 @@ crate-type = ["cdylib", "rlib"] [features] default = ["console_error_panic_hook"] + diff --git a/diesel-wasm-sqlite/build.rs b/diesel-wasm-sqlite/build.rs index 22414d535..01bfc1044 100644 --- a/diesel-wasm-sqlite/build.rs +++ b/diesel-wasm-sqlite/build.rs @@ -2,21 +2,16 @@ use std::env; use std::process::Command; fn main() { - // Tell Cargo that if the given file changes, to rerun this build script. println!("cargo::rerun-if-changed=package.js"); - // println!("cargo::rerun-if-changed=package.js"); - // can run yarn run build here too - // let out_dir = env::var("OUT_DIR").unwrap(); Command::new("yarn") .args(["run", "build"]) .status() .unwrap(); - /* - Command::new("cp") - .args(["src/wa-sqlite.wasm"]) - .arg(&format!("{}/wa-sqlite.wasm", out_dir)) - .status() - .unwrap(); - */ + + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + if target_arch != "wasm32" { + // Emit a compile error if the target is not wasm32-unknown-unknown + panic!("This crate only supports the wasm32 architecture"); + } } diff --git a/diesel-wasm-sqlite/package.js b/diesel-wasm-sqlite/package.js index a13b5fe49..1d25c662d 100644 --- a/diesel-wasm-sqlite/package.js +++ b/diesel-wasm-sqlite/package.js @@ -1,4 +1,4 @@ -import * as SQLite from "@xmtp/wa-sqlite"; +import * as WasmSQLiteLibrary from "@xmtp/wa-sqlite"; import SQLiteESMFactory from "./node_modules/@xmtp/wa-sqlite/dist/wa-sqlite.mjs"; import base64Wasm from "./node_modules/@xmtp/wa-sqlite/dist/wa-sqlite.wasm"; @@ -14,53 +14,82 @@ function base64Decode(str) { return bytes.buffer; } -const module = await SQLiteESMFactory({ - "wasmBinary": base64Decode(base64Wasm), -}); +export class SQLite { + #module; + #sqlite3; + constructor(module) { + if (typeof module === "undefined") { + throw new Error("Cannot be called directly"); + } -// const module = await initWasmModule(); -const sqlite3 = SQLite.Factory(module); + this.sqlite3 = WasmSQLiteLibrary.Factory(module); + } -export function sqlite3_result_text(context, value) { - sqlite3.result_text(context, value); -} + static async wasm_module() { + return await SQLiteESMFactory({ + "wasmBinary": base64Decode(base64Wasm), + }); + } -export function sqlite3_result_int(context, value) { - sqlite3.result_int(context, value); -} + static async build() { + const module = await SQLiteESMFactory({ + "wasmBinary": base64Decode(base64Wasm), + }); + return new WasmSQLiteLibrary(module); + } -export function sqlite3_result_int64(context, value) { - sqlite3.result_int64(context, value); -} + result_text(context, value) { + this.sqlite3.result_text(context, value); + } -export function sqlite3_result_double(context, value) { - sqlite3.result_double(context, value); -} + result_int(context, value) { + this.sqlite3.result_int(context, value); + } -export function sqlite3_result_blob(context, value) { - sqlite3.result_blob(context, value); -} + result_int64(context, value) { + this.sqlite3.result_int64(context, value); + } -export function sqlite3_result_null(context) { - sqlite3.result_null(context); -} + result_double(context, value) { + this.sqlite3.result_double(context, value); + } -export async function establish(database_url) { - try { - console.log("Opening database!", database_url); - let db = await sqlite3.open_v2(database_url); - console.log(db); - return db; - } catch { - console.log("establish err"); + result_blob(context, value) { + this.sqlite3.result_blob(context, value); + } + + result_null(context) { + this.sqlite3.result_null(context); + } + + async open_v2(database_url, iflags) { + try { + console.log("Opening database!", database_url); + let db = await this.sqlite3.open_v2(database_url, iflags); + return db; + } catch { + console.log("openv2 error"); + } + } + + async exec(db, query) { + try { + return await this.sqlite3.exec(db, query); + } catch { + console.log('exec err'); + } + } + + changes(db) { + return this.sqlite3.changes(db); } -} -export function batch_execute(database, query) { - try { - sqlite3.exec(database, query); - console.log("Batch exec'ed"); - } catch { - console.log("exec err"); + batch_execute(database, query) { + try { + sqlite3.exec(database, query); + console.log("Batch exec'ed"); + } catch { + console.log("exec err"); + } } } diff --git a/diesel-wasm-sqlite/src/connection.rs b/diesel-wasm-sqlite/src/connection.rs deleted file mode 100644 index 3f31e1532..000000000 --- a/diesel-wasm-sqlite/src/connection.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bind_collector; -pub use bind_collector::*; diff --git a/diesel-wasm-sqlite/src/connection/bind_collector.rs b/diesel-wasm-sqlite/src/connection/bind_collector.rs index 377647e03..3258aef9e 100644 --- a/diesel-wasm-sqlite/src/connection/bind_collector.rs +++ b/diesel-wasm-sqlite/src/connection/bind_collector.rs @@ -126,20 +126,16 @@ impl std::fmt::Display for InternalSqliteBindValue<'_> { impl InternalSqliteBindValue<'_> { #[allow(unsafe_code)] // ffi function calls pub(crate) fn result_of(self, ctx: &mut i32) { - use crate::ffi; + let sqlite3 = crate::get_sqlite_unchecked(); match self { - InternalSqliteBindValue::BorrowedString(s) => { - ffi::sqlite3_result_text(*ctx, s.to_string()) - } - InternalSqliteBindValue::String(s) => ffi::sqlite3_result_text(*ctx, s.to_string()), - InternalSqliteBindValue::Binary(b) => ffi::sqlite3_result_blob(*ctx, b.to_vec()), - InternalSqliteBindValue::BorrowedBinary(b) => { - ffi::sqlite3_result_blob(*ctx, b.to_vec()) - } - InternalSqliteBindValue::I32(i) => ffi::sqlite3_result_int(*ctx, i), - InternalSqliteBindValue::I64(l) => ffi::sqlite3_result_int64(*ctx, l), - InternalSqliteBindValue::F64(d) => ffi::sqlite3_result_double(*ctx, d), - InternalSqliteBindValue::Null => ffi::sqlite3_result_null(*ctx), + InternalSqliteBindValue::BorrowedString(s) => sqlite3.result_text(*ctx, s.to_string()), + InternalSqliteBindValue::String(s) => sqlite3.result_text(*ctx, s.to_string()), + InternalSqliteBindValue::Binary(b) => sqlite3.result_blob(*ctx, b.to_vec()), + InternalSqliteBindValue::BorrowedBinary(b) => sqlite3.result_blob(*ctx, b.to_vec()), + InternalSqliteBindValue::I32(i) => sqlite3.result_int(*ctx, i), + InternalSqliteBindValue::I64(l) => sqlite3.result_int64(*ctx, l), + InternalSqliteBindValue::F64(d) => sqlite3.result_double(*ctx, d), + InternalSqliteBindValue::Null => sqlite3.result_null(*ctx), } } } diff --git a/diesel-wasm-sqlite/src/connection/diesel_manage_updated_at.sql b/diesel-wasm-sqlite/src/connection/diesel_manage_updated_at.sql new file mode 100644 index 000000000..83c3d33d4 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/diesel_manage_updated_at.sql @@ -0,0 +1,11 @@ +CREATE TRIGGER __diesel_manage_updated_at_{table_name} +AFTER UPDATE ON {table_name} +FOR EACH ROW WHEN + old.updated_at IS NULL AND + new.updated_at IS NULL OR + old.updated_at == new.updated_at +BEGIN + UPDATE {table_name} + SET updated_at = CURRENT_TIMESTAMP + WHERE ROWID = new.ROWID; +END diff --git a/diesel-wasm-sqlite/src/connection/functions.rs b/diesel-wasm-sqlite/src/connection/functions.rs new file mode 100644 index 000000000..db330ee23 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/functions.rs @@ -0,0 +1,253 @@ +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 std::cell::{Ref, RefCell}; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; +use std::ops::DerefMut; +use std::rc::Rc; + +pub(super) fn register( + conn: &RawConnection, + fn_name: &str, + deterministic: bool, + mut f: F, +) -> QueryResult<()> +where + F: FnMut(&RawConnection, Args) -> Ret + std::panic::UnwindSafe + Send + 'static, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + Sqlite: HasSqlType, +{ + let fields_needed = Args::FIELD_COUNT; + if fields_needed > 127 { + return Err(Error::DatabaseError( + DatabaseErrorKind::UnableToSendCommand, + Box::new("SQLite functions cannot take more than 127 parameters".to_string()), + )); + } + + conn.register_sql_function(fn_name, fields_needed, deterministic, move |conn, args| { + let args = build_sql_function_args::(args)?; + + Ok(f(conn, args)) + })?; + Ok(()) +} + +pub(super) fn register_noargs( + conn: &RawConnection, + fn_name: &str, + deterministic: bool, + mut f: F, +) -> QueryResult<()> +where + F: FnMut() -> Ret + std::panic::UnwindSafe + Send + 'static, + Ret: ToSql, + Sqlite: HasSqlType, +{ + conn.register_sql_function(fn_name, 0, deterministic, move |_, _| Ok(f()))?; + Ok(()) +} + +pub(super) fn register_aggregate( + conn: &RawConnection, + fn_name: &str, +) -> QueryResult<()> +where + A: SqliteAggregateFunction + 'static + Send + std::panic::UnwindSafe, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + Sqlite: HasSqlType, +{ + let fields_needed = Args::FIELD_COUNT; + if fields_needed > 127 { + return Err(Error::DatabaseError( + DatabaseErrorKind::UnableToSendCommand, + Box::new("SQLite functions cannot take more than 127 parameters".to_string()), + )); + } + + conn.register_aggregate_function::( + fn_name, + fields_needed, + )?; + + Ok(()) +} + +pub(super) fn build_sql_function_args( + args: &mut [*mut ffi::sqlite3_value], +) -> Result +where + Args: FromSqlRow, +{ + let row = FunctionRow::new(args); + Args::build_from_row(&row).map_err(Error::DeserializationError) +} + +// clippy is wrong here, the let binding is required +// for lifetime reasons +#[allow(clippy::let_unit_value)] +pub(super) fn process_sql_function_result( + result: &'_ Ret, +) -> QueryResult> +where + Ret: ToSql, + Sqlite: HasSqlType, +{ + let mut metadata_lookup = (); + let value = SqliteBindValue { + inner: InternalSqliteBindValue::Null, + }; + let mut buf = Output::new(value, &mut metadata_lookup); + let is_null = result.to_sql(&mut buf).map_err(Error::SerializationError)?; + + if let IsNull::Yes = is_null { + Ok(InternalSqliteBindValue::Null) + } else { + Ok(buf.into_inner().inner) + } +} + +struct FunctionRow<'a> { + // we use `ManuallyDrop` to prevent dropping the content of the internal vector + // as this buffer is owned by sqlite not by diesel + args: Rc>>>, + field_count: usize, + marker: PhantomData<&'a ffi::sqlite3_value>, +} + +impl<'a> Drop for FunctionRow<'a> { + #[allow(unsafe_code)] // manual drop calls + fn drop(&mut self) { + if let Some(args) = Rc::get_mut(&mut self.args) { + if let PrivateSqliteRow::Duplicated { column_names, .. } = + DerefMut::deref_mut(RefCell::get_mut(args)) + { + if Rc::strong_count(column_names) == 1 { + // According the https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html#method.drop + // it's fine to just drop the values here + unsafe { std::ptr::drop_in_place(column_names as *mut _) } + } + } + } + } +} + +impl<'a> FunctionRow<'a> { + #[allow(unsafe_code)] // complicated ptr cast + fn new(args: &mut [*mut ffi::sqlite3_value]) -> 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, + column_names: Rc::from(vec![None; lengths]), + }, + ))), + marker: PhantomData, + } + } +} + +impl RowSealed for FunctionRow<'_> {} + +impl<'a> Row<'a, Sqlite> for FunctionRow<'a> { + type Field<'f> = FunctionArgument<'f> where 'a: 'f, Self: 'f; + type InnerPartialRow = Self; + + fn field_count(&self) -> usize { + self.field_count + } + + fn get<'b, I>(&'b self, idx: I) -> Option> + where + 'a: 'b, + Self: crate::row::RowIndex, + { + let idx = self.idx(idx)?; + Some(FunctionArgument { + args: self.args.borrow(), + col_idx: idx as i32, + }) + } + + fn partial_row(&self, range: std::ops::Range) -> PartialRow<'_, Self::InnerPartialRow> { + PartialRow::new(self, range) + } +} + +impl<'a> RowIndex for FunctionRow<'a> { + fn idx(&self, idx: usize) -> Option { + if idx < self.field_count() { + Some(idx) + } else { + None + } + } +} + +impl<'a, 'b> RowIndex<&'a str> for FunctionRow<'b> { + fn idx(&self, _idx: &'a str) -> Option { + None + } +} + +struct FunctionArgument<'a> { + args: Ref<'a, ManuallyDrop>>, + col_idx: i32, +} + +impl<'a> Field<'a, Sqlite> for FunctionArgument<'a> { + fn field_name(&self) -> Option<&str> { + None + } + + fn is_null(&self) -> bool { + self.value().is_none() + } + + 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 new file mode 100644 index 000000000..ca52f98b3 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/mod.rs @@ -0,0 +1,636 @@ +mod bind_collector; +// mod functions; +// mod owned_row; +mod raw; +// mod row; +// mod serialized_database; +// mod sqlite_value; +// mod statement_iterator; +// mod stmt; + +pub(crate) use self::bind_collector::SqliteBindCollector; +pub use self::bind_collector::SqliteBindValue; +// pub use self::serialized_database::SerializedDatabase; +// pub use self::sqlite_value::SqliteValue; + +/* +use self::raw::RawConnection; +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::connection::*; +use crate::query_builder::*; +use crate::sqlite::WasmSqlite; +use diesel::deserialize::{FromSqlRow, StaticallySizedRow}; +use diesel::expression::QueryMetadata; +use diesel::result::*; +use diesel::serialize::ToSql; +use diesel::sql_types::{HasSqlType, TypeMetadata}; +*/ +use diesel::{ + backend::Backend, + connection::Instrumentation, + query_builder::{AsQuery, QueryFragment, QueryId}, + result::QueryResult, + row::Field, +}; +use diesel_async::{AnsiTransactionManager, AsyncConnection, SimpleAsyncConnection}; +use futures::stream::Stream; +use std::{ + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, +}; + +use crate::{get_sqlite, get_sqlite_unchecked, WasmSqlite, WasmSqliteError}; +use std::future::Ready; + +unsafe impl Send for WasmSqliteConnection {} +#[derive(Debug)] +pub struct WasmSqliteConnection { + raw: raw::RawConnection, +} + +#[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) + .map_err(WasmSqliteError::from) + .map_err(Into::into) + } +} + +/// TODO: The placeholder stuff all needs to be re-done + +pub struct OwnedSqliteFieldPlaceholder<'field> { + field: PhantomData<&'field ()>, +} + +impl<'f> Field<'f, WasmSqlite> for OwnedSqliteFieldPlaceholder<'f> { + fn field_name(&self) -> Option<&str> { + Some("placeholder") + } + fn value(&self) -> Option<::RawValue<'_>> { + todo!() + } +} + +pub struct InnerPartialRowPlaceholder; + +pub struct RowReady(Option<()>); + +impl std::future::Future for RowReady { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Poll::Ready(self.0.take().expect("`Ready` polled after completion")) + } +} + +impl diesel::row::RowSealed for RowReady {} + +impl<'a, 'b> diesel::row::RowIndex<&'a str> for RowReady { + fn idx(&self, idx: &'a str) -> Option { + todo!() + } +} + +impl diesel::row::RowIndex for RowReady { + fn idx(&self, idx: usize) -> Option { + todo!() + } +} + +impl<'a> diesel::row::Row<'a, WasmSqlite> for RowReady { + type Field<'f> = OwnedSqliteFieldPlaceholder<'f> + where + 'a: 'f, + Self: 'f; + + type InnerPartialRow = Self; + + fn field_count(&self) -> usize { + todo!() + } + + fn get<'b, I>(&'b self, idx: I) -> Option> + where + 'a: 'b, + Self: diesel::row::RowIndex, + { + todo!() + } + + fn partial_row( + &self, + range: std::ops::Range, + ) -> diesel::row::PartialRow<'_, Self::InnerPartialRow> { + todo!() + } +} + +pub struct RowReadyStreamPlaceholder; + +impl Stream for RowReadyStreamPlaceholder { + type Item = QueryResult; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + todo!(); + } +} + +impl diesel::connection::ConnectionSealed for WasmSqliteConnection {} + +#[async_trait::async_trait(?Send)] +impl AsyncConnection for WasmSqliteConnection { + type Backend = WasmSqlite; + type TransactionManager = AnsiTransactionManager; + // placeholders + type ExecuteFuture<'conn, 'query> = Ready>; + type LoadFuture<'conn, 'query> = Ready>>; + type Stream<'conn, 'query> = RowReadyStreamPlaceholder; + type Row<'conn, 'query> = RowReady; + + async fn establish(database_url: &str) -> diesel::prelude::ConnectionResult { + Ok(WasmSqliteConnection { + raw: raw::RawConnection::establish(database_url).await.unwrap(), + }) + } + + fn load<'conn, 'query, T>(&'conn mut self, source: T) -> Self::LoadFuture<'conn, 'query> + where + T: AsQuery + 'query, + T::Query: QueryFragment + QueryId + 'query, + { + todo!() + } + + fn execute_returning_count<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> Self::ExecuteFuture<'conn, 'query> + where + T: QueryFragment + QueryId + 'query, + { + todo!() + } + + fn transaction_state( + &mut self, + ) -> &mut >::TransactionStateData{ + todo!() + } + + fn instrumentation(&mut self) -> &mut dyn Instrumentation { + todo!() + } + + fn set_instrumentation(&mut self, instrumentation: impl Instrumentation) { + todo!() + } +} + +/* +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 { + type Cursor<'conn, 'query> = StatementIterator<'conn, 'query>; + type Row<'conn, 'query> = self::row::SqliteRow<'conn, 'query>; + + fn load<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> QueryResult> + where + T: Query + QueryFragment + QueryId + 'query, + Self::Backend: QueryMetadata, + { + let statement = self.prepared_query(source)?; + + Ok(StatementIterator::new(statement)) + } +} + +impl WithMetadataLookup for SqliteConnection { + fn metadata_lookup(&mut self) -> &mut ::MetadataLookup { + &mut self.metadata_lookup + } +} + +#[cfg(feature = "r2d2")] +impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection { + fn ping(&mut self) -> QueryResult<()> { + use crate::RunQueryDsl; + + crate::r2d2::CheckConnectionQuery.execute(self).map(|_| ()) + } + + fn is_broken(&mut self) -> bool { + AnsiTransactionManager::is_broken_transaction_manager(self) + } +} + +impl MultiConnectionHelper for SqliteConnection { + fn to_any<'a>( + lookup: &mut ::MetadataLookup, + ) -> &mut (dyn std::any::Any + 'a) { + lookup + } + + fn from_any( + lookup: &mut dyn std::any::Any, + ) -> Option<&mut ::MetadataLookup> { + lookup.downcast_mut() + } +} + +impl SqliteConnection { + /// Run a transaction with `BEGIN IMMEDIATE` + /// + /// This method will return an error if a transaction is already open. + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # run_test().unwrap(); + /// # } + /// # + /// # fn run_test() -> QueryResult<()> { + /// # let mut conn = SqliteConnection::establish(":memory:").unwrap(); + /// conn.immediate_transaction(|conn| { + /// // Do stuff in a transaction + /// Ok(()) + /// }) + /// # } + /// ``` + pub fn immediate_transaction(&mut self, f: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + E: From, + { + self.transaction_sql(f, "BEGIN IMMEDIATE") + } + + /// Run a transaction with `BEGIN EXCLUSIVE` + /// + /// This method will return an error if a transaction is already open. + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # run_test().unwrap(); + /// # } + /// # + /// # fn run_test() -> QueryResult<()> { + /// # let mut conn = SqliteConnection::establish(":memory:").unwrap(); + /// conn.exclusive_transaction(|conn| { + /// // Do stuff in a transaction + /// Ok(()) + /// }) + /// # } + /// ``` + pub fn exclusive_transaction(&mut self, f: F) -> Result + where + F: FnOnce(&mut Self) -> Result, + E: From, + { + self.transaction_sql(f, "BEGIN EXCLUSIVE") + } + + 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)?; + match f(&mut *self) { + Ok(value) => { + AnsiTransactionManager::commit_transaction(&mut *self)?; + Ok(value) + } + Err(e) => { + AnsiTransactionManager::rollback_transaction(&mut *self)?; + Err(e) + } + } + } + + fn prepared_query<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> QueryResult> + where + 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( + &source, + &Sqlite, + &[], + |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, + fn_name: &str, + deterministic: bool, + mut f: F, + ) -> QueryResult<()> + where + F: FnMut(Args) -> Ret + std::panic::UnwindSafe + Send + 'static, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + Sqlite: HasSqlType, + { + functions::register( + &self.raw_connection, + fn_name, + deterministic, + move |_, args| f(args), + ) + } + + #[doc(hidden)] + pub fn register_noarg_sql_function( + &self, + fn_name: &str, + deterministic: bool, + f: F, + ) -> QueryResult<()> + where + F: FnMut() -> Ret + std::panic::UnwindSafe + Send + 'static, + Ret: ToSql, + Sqlite: HasSqlType, + { + functions::register_noargs(&self.raw_connection, fn_name, deterministic, f) + } + + #[doc(hidden)] + pub fn register_aggregate_function( + &mut self, + fn_name: &str, + ) -> QueryResult<()> + where + A: SqliteAggregateFunction + 'static + Send + std::panic::UnwindSafe, + Args: FromSqlRow + StaticallySizedRow, + Ret: ToSql, + Sqlite: HasSqlType, + { + functions::register_aggregate::<_, _, _, _, A>(&self.raw_connection, fn_name) + } + + /// Register a collation function. + /// + /// `collation` must always return the same answer given the same inputs. + /// If `collation` panics and unwinds the stack, the process is aborted, since it is used + /// across a C FFI boundary, which cannot be unwound across and there is no way to + /// signal failures via the SQLite interface in this case.. + /// + /// If the name is already registered it will be overwritten. + /// + /// This method will return an error if registering the function fails, either due to an + /// out-of-memory situation or because a collation with that name already exists and is + /// currently being used in parallel by a query. + /// + /// The collation needs to be specified when creating a table: + /// `CREATE TABLE my_table ( str TEXT COLLATE MY_COLLATION )`, + /// where `MY_COLLATION` corresponds to name passed as `collation_name`. + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # run_test().unwrap(); + /// # } + /// # + /// # fn run_test() -> QueryResult<()> { + /// # let mut conn = SqliteConnection::establish(":memory:").unwrap(); + /// // sqlite NOCASE only works for ASCII characters, + /// // this collation allows handling UTF-8 (barring locale differences) + /// conn.register_collation("RUSTNOCASE", |rhs, lhs| { + /// rhs.to_lowercase().cmp(&lhs.to_lowercase()) + /// }) + /// # } + /// ``` + pub fn register_collation(&mut self, collation_name: &str, collation: F) -> QueryResult<()> + where + F: Fn(&str, &str) -> std::cmp::Ordering + Send + 'static + std::panic::UnwindSafe, + { + 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 + /// was saved in a file. + /// + /// # Returns + /// + /// This function returns a byte slice representing the serialized database. + 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. + /// If successful, the database is loaded into the connection. If the deserialization fails, + /// an error is returned. + /// + /// The database is opened in READONLY mode. + /// + /// # Example + /// + /// ```no_run + /// # use diesel::sqlite::SerializedDatabase; + /// # use diesel::sqlite::SqliteConnection; + /// # use diesel::result::QueryResult; + /// # use diesel::sql_query; + /// # use diesel::Connection; + /// # use diesel::RunQueryDsl; + /// # fn main() { + /// let connection = &mut SqliteConnection::establish(":memory:").unwrap(); + /// + /// sql_query("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)") + /// .execute(connection).unwrap(); + /// sql_query("INSERT INTO users (name, email) VALUES ('John Doe', 'john.doe@example.com'), ('Jane Doe', 'jane.doe@example.com')") + /// .execute(connection).unwrap(); + /// + /// // Serialize the database to a byte vector + /// let serialized_db: SerializedDatabase = connection.serialize_database_to_buffer(); + /// + /// // Create a new in-memory SQLite database + /// let connection = &mut SqliteConnection::establish(":memory:").unwrap(); + /// + /// // Deserialize the byte vector into the new database + /// connection.deserialize_readonly_database_from_buffer(serialized_db.as_slice()).unwrap(); + /// # + /// # } + /// ``` + 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}; + + functions::register::( + &self.raw_connection, + "diesel_manage_updated_at", + false, + |conn, table_name: String| { + conn.exec(&format!( + include_str!("diesel_manage_updated_at.sql"), + table_name = table_name + )) + .expect("Failed to create trigger"); + 0 // have to return *something* + }, + ) + } + + fn establish_inner(database_url: &str) -> Result { + use crate::result::ConnectionError::CouldntSetupConfiguration; + let raw_connection = RawConnection::establish(database_url)?; + let conn = Self { + statement_cache: StatementCache::new(), + raw_connection, + transaction_state: AnsiTransactionManager::default(), + metadata_lookup: (), + instrumentation: None, + }; + conn.register_diesel_sql_functions() + .map_err(CouldntSetupConfiguration)?; + Ok(conn) + } +} +*/ + +/* +fn error_message(err_code: i32) -> &'static str { + let sqlite3 = crate::get_sqlite_unchecked(); + sqlite3.code_to_str(err_code) +} +*/ diff --git a/diesel-wasm-sqlite/src/connection/owned_row.rs b/diesel-wasm-sqlite/src/connection/owned_row.rs new file mode 100644 index 000000000..43e225d0e --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/owned_row.rs @@ -0,0 +1,92 @@ +use std::sync::Arc; + +use super::sqlite_value::{OwnedSqliteValue, SqliteValue}; +use crate::backend::Backend; +use crate::row::{Field, PartialRow, Row, RowIndex, RowSealed}; +use crate::sqlite::Sqlite; + +#[derive(Debug)] +pub struct OwnedSqliteRow { + pub(super) values: Vec>, + column_names: Arc<[Option]>, +} + +impl OwnedSqliteRow { + pub(super) fn new( + values: Vec>, + column_names: Arc<[Option]>, + ) -> Self { + OwnedSqliteRow { + values, + column_names, + } + } +} + +impl RowSealed for OwnedSqliteRow {} + +impl<'a> Row<'a, Sqlite> for OwnedSqliteRow { + type Field<'field> = OwnedSqliteField<'field> where 'a: 'field, Self: 'field; + type InnerPartialRow = Self; + + fn field_count(&self) -> usize { + self.values.len() + } + + fn get<'field, I>(&'field self, idx: I) -> Option> + where + 'a: 'field, + Self: RowIndex, + { + let idx = self.idx(idx)?; + Some(OwnedSqliteField { + row: self, + col_idx: i32::try_from(idx).ok()?, + }) + } + + fn partial_row(&self, range: std::ops::Range) -> PartialRow<'_, Self::InnerPartialRow> { + PartialRow::new(self, range) + } +} + +impl RowIndex for OwnedSqliteRow { + fn idx(&self, idx: usize) -> Option { + if idx < self.field_count() { + Some(idx) + } else { + None + } + } +} + +impl<'idx> RowIndex<&'idx str> for OwnedSqliteRow { + fn idx(&self, field_name: &'idx str) -> Option { + self.column_names + .iter() + .position(|n| n.as_ref().map(|s| s as &str) == Some(field_name)) + } +} + +#[allow(missing_debug_implementations)] +pub struct OwnedSqliteField<'row> { + pub(super) row: &'row OwnedSqliteRow, + pub(super) col_idx: i32, +} + +impl<'row> Field<'row, Sqlite> for OwnedSqliteField<'row> { + fn field_name(&self) -> Option<&str> { + self.row + .column_names + .get(self.col_idx as usize) + .and_then(|o| o.as_ref().map(|s| s.as_ref())) + } + + fn is_null(&self) -> bool { + self.value().is_none() + } + + fn value(&self) -> Option<::RawValue<'row>> { + SqliteValue::from_owned_row(self.row, self.col_idx) + } +} diff --git a/diesel-wasm-sqlite/src/connection/raw.rs b/diesel-wasm-sqlite/src/connection/raw.rs new file mode 100644 index 000000000..c0e20dbc7 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/raw.rs @@ -0,0 +1,606 @@ +#![allow(unsafe_code)] // ffi calls + +// use std::ffi::{CString, NulError}; +// use std::io::{stderr, Write}; +// use std::os::raw as libc; +// use std::ptr::NonNull; +// use std::{mem, ptr, slice, str}; + +// use super::functions::{build_sql_function_args, process_sql_function_result}; +// use super::serialized_database::SerializedDatabase; +// use super::stmt::ensure_sqlite_ok; +// use super::{Sqlite, SqliteAggregateFunction}; +// use crate::deserialize::FromSqlRow; +// use crate::result::Error::DatabaseError; +use crate::sqlite_types::SqliteOpenFlags; +use diesel::result::*; +use diesel::serialize::ToSql; +use diesel::sql_types::HasSqlType; +use wasm_bindgen::JsValue; +/* +/// For use in FFI function, which cannot unwind. +/// Print the message, ask to open an issue at Github and [`abort`](std::process::abort). +macro_rules! assert_fail { + ($fmt:expr $(,$args:tt)*) => { + eprint!(concat!( + $fmt, + "If you see this message, please open an issue at https://github.com/diesel-rs/diesel/issues/new.\n", + "Source location: {}:{}\n", + ), $($args,)* file!(), line!()); + std::process::abort() + }; +} +*/ + +#[allow(missing_copy_implementations)] +#[derive(Debug)] +pub(super) struct RawConnection { + pub(super) internal_connection: JsValue, +} + +impl RawConnection { + pub(super) async fn establish(database_url: &str) -> ConnectionResult { + let sqlite3 = crate::get_sqlite().await; + let database_url = if database_url.starts_with("sqlite://") { + database_url.replacen("sqlite://", "file:", 1) + } else { + database_url.to_string() + }; + let flags = SqliteOpenFlags::SQLITE_OPEN_READWRITE + | SqliteOpenFlags::SQLITE_OPEN_CREATE + | SqliteOpenFlags::SQLITE_OPEN_URI; + + Ok(RawConnection { + internal_connection: sqlite3 + .open_v2(&database_url, Some(flags.bits() as i32)) + .await + .unwrap(), + }) + } + + pub(super) async fn exec(&self, query: &str) -> QueryResult<()> { + let sqlite3 = crate::get_sqlite().await; + let result = sqlite3 + .exec(&self.internal_connection, query) + .await + .unwrap(); + + Ok(result) + } + + pub(super) fn rows_affected_by_last_query(&self) -> usize { + let sqlite3 = crate::get_sqlite_unchecked(); + sqlite3.changes(&self.internal_connection) + } + + /* + pub(super) fn register_sql_function( + &self, + fn_name: &str, + num_args: usize, + deterministic: bool, + f: F, + ) -> QueryResult<()> + where + F: FnMut(&Self, &mut [*mut ffi::sqlite3_value]) -> QueryResult + + std::panic::UnwindSafe + + Send + + 'static, + Ret: ToSql, + Sqlite: HasSqlType, + { + let callback_fn = Box::into_raw(Box::new(CustomFunctionUserPtr { + callback: f, + function_name: fn_name.to_owned(), + })); + let fn_name = Self::get_fn_name(fn_name)?; + let flags = Self::get_flags(deterministic); + + let result = unsafe { + ffi::sqlite3_create_function_v2( + self.internal_connection.as_ptr(), + fn_name.as_ptr(), + num_args as _, + flags, + callback_fn as *mut _, + Some(run_custom_function::), + None, + None, + Some(destroy_boxed::>), + ) + }; + + Self::process_sql_function_result(result) + } + + pub(super) fn register_aggregate_function( + &self, + fn_name: &str, + num_args: usize, + ) -> QueryResult<()> + where + A: SqliteAggregateFunction + 'static + Send + std::panic::UnwindSafe, + Args: FromSqlRow, + Ret: ToSql, + Sqlite: HasSqlType, + { + let fn_name = Self::get_fn_name(fn_name)?; + let flags = Self::get_flags(false); + + let result = unsafe { + ffi::sqlite3_create_function_v2( + self.internal_connection.as_ptr(), + fn_name.as_ptr(), + num_args as _, + flags, + ptr::null_mut(), + None, + Some(run_aggregator_step_function::<_, _, _, _, A>), + Some(run_aggregator_final_function::<_, _, _, _, A>), + None, + ) + }; + + Self::process_sql_function_result(result) + } + + pub(super) fn register_collation_function( + &self, + collation_name: &str, + collation: F, + ) -> QueryResult<()> + where + F: Fn(&str, &str) -> std::cmp::Ordering + std::panic::UnwindSafe + Send + 'static, + { + let callback_fn = Box::into_raw(Box::new(CollationUserPtr { + callback: collation, + collation_name: collation_name.to_owned(), + })); + let collation_name = Self::get_fn_name(collation_name)?; + + let result = unsafe { + ffi::sqlite3_create_collation_v2( + self.internal_connection.as_ptr(), + collation_name.as_ptr(), + ffi::SQLITE_UTF8, + callback_fn as *mut _, + Some(run_collation_function::), + Some(destroy_boxed::>), + ) + }; + + let result = Self::process_sql_function_result(result); + if result.is_err() { + destroy_boxed::>(callback_fn as *mut _); + } + result + } + + 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()) + } + } + + fn get_fn_name(fn_name: &str) -> Result { + CString::new(fn_name) + } + + fn get_flags(deterministic: bool) -> i32 { + let mut flags = ffi::SQLITE_UTF8; + if deterministic { + flags |= ffi::SQLITE_DETERMINISTIC; + } + flags + } + + fn process_sql_function_result(result: i32) -> Result<(), Error> { + if result == ffi::SQLITE_OK { + Ok(()) + } else { + let error_message = super::error_message(result); + Err(DatabaseError( + DatabaseErrorKind::Unknown, + Box::new(error_message.to_string()), + )) + } + } + */ +} +/* +impl Drop for RawConnection { + fn drop(&mut self) { + use std::thread::panicking; + + let sqlite3 = crate::get_sqlite_unchecked(); + + let close_result = sqlite3.close(self.internal_connection); + + if close_result != ffi::SQLITE_OK { + let error_message = super::error_message(close_result); + if panicking() { + write!(stderr(), "Error closing SQLite connection: {error_message}") + .expect("Error writing to `stderr`"); + } else { + panic!("Error closing SQLite connection: {}", error_message); + } + } + } +} +*/ + +/* +enum SqliteCallbackError { + Abort(&'static str), + DieselError(crate::result::Error), + Panic(String), +} + +impl SqliteCallbackError { + fn emit(&self, ctx: *mut ffi::sqlite3_context) { + let s; + let msg = match self { + SqliteCallbackError::Abort(msg) => *msg, + SqliteCallbackError::DieselError(e) => { + s = e.to_string(); + &s + } + SqliteCallbackError::Panic(msg) => msg, + }; + unsafe { + context_error_str(ctx, msg); + } + } +} + +impl From for SqliteCallbackError { + fn from(e: crate::result::Error) -> Self { + Self::DieselError(e) + } +} + +struct CustomFunctionUserPtr { + callback: F, + function_name: String, +} + +#[allow(warnings)] +extern "C" fn run_custom_function( + ctx: *mut ffi::sqlite3_context, + num_args: libc::c_int, + value_ptr: *mut *mut ffi::sqlite3_value, +) where + F: FnMut(&RawConnection, &mut [*mut ffi::sqlite3_value]) -> QueryResult + + std::panic::UnwindSafe + + Send + + 'static, + Ret: ToSql, + Sqlite: HasSqlType, +{ + use std::ops::Deref; + static NULL_DATA_ERR: &str = "An unknown error occurred. sqlite3_user_data returned a null pointer. This should never happen."; + static NULL_CONN_ERR: &str = "An unknown error occurred. sqlite3_context_db_handle returned a null pointer. This should never happen."; + + let conn = match unsafe { NonNull::new(ffi::sqlite3_context_db_handle(ctx)) } { + // We use `ManuallyDrop` here because we do not want to run the + // Drop impl of `RawConnection` as this would close the connection + Some(conn) => mem::ManuallyDrop::new(RawConnection { + internal_connection: conn, + }), + None => { + unsafe { context_error_str(ctx, NULL_CONN_ERR) }; + return; + } + }; + + let data_ptr = unsafe { ffi::sqlite3_user_data(ctx) }; + + let mut data_ptr = match NonNull::new(data_ptr as *mut CustomFunctionUserPtr) { + None => unsafe { + context_error_str(ctx, NULL_DATA_ERR); + return; + }, + Some(mut f) => f, + }; + let data_ptr = unsafe { data_ptr.as_mut() }; + + // We need this to move the reference into the catch_unwind part + // this is sound as `F` itself and the stored string is `UnwindSafe` + let callback = std::panic::AssertUnwindSafe(&mut data_ptr.callback); + + let result = std::panic::catch_unwind(move || { + let _ = &callback; + let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_args as _) }; + let res = (callback.0)(&*conn, args)?; + let value = process_sql_function_result(&res)?; + // We've checked already that ctx is not null + unsafe { + value.result_of(&mut *ctx); + } + Ok(()) + }) + .unwrap_or_else(|p| Err(SqliteCallbackError::Panic(data_ptr.function_name.clone()))); + if let Err(e) = result { + e.emit(ctx); + } +} + +// Need a custom option type here, because the std lib one does not have guarantees about the discriminate values +// See: https://github.com/rust-lang/rfcs/blob/master/text/2195-really-tagged-unions.md#opaque-tags +#[repr(u8)] +enum OptionalAggregator { + // Discriminant is 0 + None, + Some(A), +} + +#[allow(warnings)] +extern "C" fn run_aggregator_step_function( + ctx: *mut ffi::sqlite3_context, + num_args: libc::c_int, + value_ptr: *mut *mut ffi::sqlite3_value, +) where + A: SqliteAggregateFunction + 'static + Send + std::panic::UnwindSafe, + Args: FromSqlRow, + Ret: ToSql, + Sqlite: HasSqlType, +{ + let result = std::panic::catch_unwind(move || { + let args = unsafe { slice::from_raw_parts_mut(value_ptr, num_args as _) }; + run_aggregator_step::(ctx, args) + }) + .unwrap_or_else(|e| { + Err(SqliteCallbackError::Panic(format!( + "{}::step() panicked", + std::any::type_name::() + ))) + }); + + match result { + Ok(()) => {} + Err(e) => e.emit(ctx), + } +} + +fn run_aggregator_step( + ctx: *mut ffi::sqlite3_context, + args: &mut [*mut ffi::sqlite3_value], +) -> Result<(), SqliteCallbackError> +where + A: SqliteAggregateFunction, + Args: FromSqlRow, +{ + static NULL_AG_CTX_ERR: &str = "An unknown error occurred. sqlite3_aggregate_context returned a null pointer. This should never happen."; + static NULL_CTX_ERR: &str = + "We've written the aggregator to the aggregate context, but it could not be retrieved."; + + let aggregate_context = unsafe { + // This block of unsafe code makes the following assumptions: + // + // * sqlite3_aggregate_context allocates sizeof::> + // bytes of zeroed memory as documented here: + // https://www.sqlite.org/c3ref/aggregate_context.html + // A null pointer is returned for negative or zero sized types, + // which should be impossible in theory. We check that nevertheless + // + // * OptionalAggregator::None has a discriminant of 0 as specified by + // #[repr(u8)] + RFC 2195 + // + // * If all bytes are zero, the discriminant is also zero, so we can + // assume that we get OptionalAggregator::None in this case. This is + // not UB as we only access the discriminant here, so we do not try + // to read any other zeroed memory. After that we initialize our enum + // by writing a correct value at this location via ptr::write_unaligned + // + // * We use ptr::write_unaligned as we did not found any guarantees that + // the memory will have a correct alignment. + // (Note I(weiznich): would assume that it is aligned correctly, but we + // we cannot guarantee it, so better be safe than sorry) + ffi::sqlite3_aggregate_context(ctx, std::mem::size_of::>() as i32) + }; + let aggregate_context = NonNull::new(aggregate_context as *mut OptionalAggregator); + let aggregator = unsafe { + match aggregate_context.map(|a| &mut *a.as_ptr()) { + Some(&mut OptionalAggregator::Some(ref mut agg)) => agg, + Some(a_ptr @ &mut OptionalAggregator::None) => { + ptr::write_unaligned(a_ptr as *mut _, OptionalAggregator::Some(A::default())); + if let OptionalAggregator::Some(ref mut agg) = a_ptr { + agg + } else { + return Err(SqliteCallbackError::Abort(NULL_CTX_ERR)); + } + } + None => { + return Err(SqliteCallbackError::Abort(NULL_AG_CTX_ERR)); + } + } + }; + let args = build_sql_function_args::(args)?; + + aggregator.step(args); + Ok(()) +} + +extern "C" fn run_aggregator_final_function( + ctx: *mut ffi::sqlite3_context, +) where + A: SqliteAggregateFunction + 'static + Send, + Args: FromSqlRow, + Ret: ToSql, + Sqlite: HasSqlType, +{ + static NO_AGGREGATOR_FOUND: &str = "We've written to the aggregator in the xStep callback. If xStep was never called, then ffi::sqlite_aggregate_context() would have returned a NULL pointer."; + let aggregate_context = unsafe { + // Within the xFinal callback, it is customary to set nBytes to 0 so no pointless memory + // allocations occur, a null pointer is returned in this case + // See: https://www.sqlite.org/c3ref/aggregate_context.html + // + // For the reasoning about the safety of the OptionalAggregator handling + // see the comment in run_aggregator_step_function. + ffi::sqlite3_aggregate_context(ctx, 0) + }; + + let result = std::panic::catch_unwind(|| { + let mut aggregate_context = NonNull::new(aggregate_context as *mut OptionalAggregator); + + let aggregator = if let Some(a) = aggregate_context.as_mut() { + let a = unsafe { a.as_mut() }; + match std::mem::replace(a, OptionalAggregator::None) { + OptionalAggregator::None => { + return Err(SqliteCallbackError::Abort(NO_AGGREGATOR_FOUND)); + } + OptionalAggregator::Some(a) => Some(a), + } + } else { + None + }; + + let res = A::finalize(aggregator); + let value = process_sql_function_result(&res)?; + // We've checked already that ctx is not null + unsafe { + value.result_of(&mut *ctx); + } + Ok(()) + }) + .unwrap_or_else(|_e| { + Err(SqliteCallbackError::Panic(format!( + "{}::finalize() panicked", + std::any::type_name::() + ))) + }); + if let Err(e) = result { + e.emit(ctx); + } +} + +unsafe fn context_error_str(ctx: *mut ffi::sqlite3_context, error: &str) { + ffi::sqlite3_result_error(ctx, error.as_ptr() as *const _, error.len() as _); +} + +struct CollationUserPtr { + callback: F, + collation_name: String, +} + +#[allow(warnings)] +extern "C" fn run_collation_function( + user_ptr: *mut libc::c_void, + lhs_len: libc::c_int, + lhs_ptr: *const libc::c_void, + rhs_len: libc::c_int, + rhs_ptr: *const libc::c_void, +) -> libc::c_int +where + F: Fn(&str, &str) -> std::cmp::Ordering + Send + std::panic::UnwindSafe + 'static, +{ + let user_ptr = user_ptr as *const CollationUserPtr; + let user_ptr = std::panic::AssertUnwindSafe(unsafe { user_ptr.as_ref() }); + + let result = std::panic::catch_unwind(|| { + let user_ptr = user_ptr.ok_or_else(|| { + SqliteCallbackError::Abort( + "Got a null pointer as data pointer. This should never happen", + ) + })?; + for (ptr, len, side) in &[(rhs_ptr, rhs_len, "rhs"), (lhs_ptr, lhs_len, "lhs")] { + if *len < 0 { + assert_fail!( + "An unknown error occurred. {}_len is negative. This should never happen.", + side + ); + } + if ptr.is_null() { + assert_fail!( + "An unknown error occurred. {}_ptr is a null pointer. This should never happen.", + side + ); + } + } + + let (rhs, lhs) = unsafe { + // Depending on the eTextRep-parameter to sqlite3_create_collation_v2() the strings can + // have various encodings. register_collation_function() always selects SQLITE_UTF8, so the + // pointers point to valid UTF-8 strings (assuming correct behavior of libsqlite3). + ( + str::from_utf8(slice::from_raw_parts(rhs_ptr as *const u8, rhs_len as _)), + str::from_utf8(slice::from_raw_parts(lhs_ptr as *const u8, lhs_len as _)), + ) + }; + + let rhs = + rhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for rhs"))?; + let lhs = + lhs.map_err(|_| SqliteCallbackError::Abort("Got an invalid UTF-8 string for lhs"))?; + + Ok((user_ptr.callback)(rhs, lhs)) + }) + .unwrap_or_else(|p| { + Err(SqliteCallbackError::Panic( + user_ptr + .map(|u| u.collation_name.clone()) + .unwrap_or_default(), + )) + }); + + match result { + Ok(std::cmp::Ordering::Less) => -1, + Ok(std::cmp::Ordering::Equal) => 0, + Ok(std::cmp::Ordering::Greater) => 1, + Err(SqliteCallbackError::Abort(a)) => { + eprintln!( + "Collation function {} failed with: {}", + user_ptr + .map(|c| &c.collation_name as &str) + .unwrap_or_default(), + a + ); + std::process::abort() + } + Err(SqliteCallbackError::DieselError(e)) => { + eprintln!( + "Collation function {} failed with: {}", + user_ptr + .map(|c| &c.collation_name as &str) + .unwrap_or_default(), + e + ); + std::process::abort() + } + Err(SqliteCallbackError::Panic(msg)) => { + eprintln!("Collation function {} panicked", msg); + std::process::abort() + } + } +} + +extern "C" fn destroy_boxed(data: *mut libc::c_void) { + let ptr = data as *mut F; + unsafe { std::mem::drop(Box::from_raw(ptr)) }; +} + +*/ diff --git a/diesel-wasm-sqlite/src/connection/row.rs b/diesel-wasm-sqlite/src/connection/row.rs new file mode 100644 index 000000000..75cc27369 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/row.rs @@ -0,0 +1,408 @@ +use std::cell::{Ref, RefCell}; +use std::rc::Rc; +use std::sync::Arc; + +use super::owned_row::OwnedSqliteRow; +use super::sqlite_value::{OwnedSqliteValue, SqliteValue}; +use super::stmt::StatementUse; +use crate::backend::Backend; +use crate::row::{Field, IntoOwnedRow, PartialRow, Row, RowIndex, RowSealed}; +use crate::sqlite::Sqlite; + +#[allow(missing_debug_implementations)] +pub struct SqliteRow<'stmt, 'query> { + pub(super) inner: Rc>>, + pub(super) field_count: usize, +} + +pub(super) enum PrivateSqliteRow<'stmt, 'query> { + Direct(StatementUse<'stmt, 'query>), + Duplicated { + values: Vec>, + column_names: Rc<[Option]>, + }, +} + +impl<'stmt> IntoOwnedRow<'stmt, Sqlite> for SqliteRow<'stmt, '_> { + type OwnedRow = OwnedSqliteRow; + + type Cache = Option]>>; + + fn into_owned(self, column_name_cache: &mut Self::Cache) -> Self::OwnedRow { + self.inner.borrow().moveable(column_name_cache) + } +} + +impl<'stmt, 'query> PrivateSqliteRow<'stmt, 'query> { + pub(super) fn duplicate( + &mut self, + column_names: &mut Option]>>, + ) -> PrivateSqliteRow<'stmt, 'query> { + match self { + PrivateSqliteRow::Direct(stmt) => { + let column_names = if let Some(column_names) = column_names { + column_names.clone() + } else { + let c: Rc<[Option]> = Rc::from( + (0..stmt.column_count()) + .map(|idx| stmt.field_name(idx).map(|s| s.to_owned())) + .collect::>(), + ); + *column_names = Some(c.clone()); + c + }; + PrivateSqliteRow::Duplicated { + values: (0..stmt.column_count()) + .map(|idx| stmt.copy_value(idx)) + .collect(), + column_names, + } + } + PrivateSqliteRow::Duplicated { + values, + column_names, + } => PrivateSqliteRow::Duplicated { + values: values + .iter() + .map(|v| v.as_ref().map(|v| v.duplicate())) + .collect(), + column_names: column_names.clone(), + }, + } + } + + pub(super) fn moveable( + &self, + column_name_cache: &mut Option]>>, + ) -> OwnedSqliteRow { + match self { + PrivateSqliteRow::Direct(stmt) => { + if column_name_cache.is_none() { + *column_name_cache = Some( + (0..stmt.column_count()) + .map(|idx| stmt.field_name(idx).map(|s| s.to_owned())) + .collect::>() + .into(), + ); + } + let column_names = Arc::clone( + column_name_cache + .as_ref() + .expect("This is initialized above"), + ); + OwnedSqliteRow::new( + (0..stmt.column_count()) + .map(|idx| stmt.copy_value(idx)) + .collect(), + column_names, + ) + } + PrivateSqliteRow::Duplicated { + values, + column_names, + } => { + if column_name_cache.is_none() { + *column_name_cache = Some( + (*column_names) + .iter() + .map(|s| s.to_owned()) + .collect::>() + .into(), + ); + } + let column_names = Arc::clone( + column_name_cache + .as_ref() + .expect("This is initialized above"), + ); + OwnedSqliteRow::new( + values + .iter() + .map(|v| v.as_ref().map(|v| v.duplicate())) + .collect(), + column_names, + ) + } + } + } +} + +impl<'stmt, 'query> RowSealed for SqliteRow<'stmt, 'query> {} + +impl<'stmt, 'query> Row<'stmt, Sqlite> for SqliteRow<'stmt, 'query> { + type Field<'field> = SqliteField<'field, 'field> where 'stmt: 'field, Self: 'field; + type InnerPartialRow = Self; + + fn field_count(&self) -> usize { + self.field_count + } + + fn get<'field, I>(&'field self, idx: I) -> Option> + where + 'stmt: 'field, + Self: RowIndex, + { + let idx = self.idx(idx)?; + Some(SqliteField { + row: self.inner.borrow(), + col_idx: i32::try_from(idx).ok()?, + }) + } + + fn partial_row(&self, range: std::ops::Range) -> PartialRow<'_, Self::InnerPartialRow> { + PartialRow::new(self, range) + } +} + +impl<'stmt, 'query> RowIndex for SqliteRow<'stmt, 'query> { + fn idx(&self, idx: usize) -> Option { + if idx < self.field_count { + Some(idx) + } else { + None + } + } +} + +impl<'stmt, 'idx, 'query> RowIndex<&'idx str> for SqliteRow<'stmt, 'query> { + fn idx(&self, field_name: &'idx str) -> Option { + match &mut *self.inner.borrow_mut() { + PrivateSqliteRow::Direct(stmt) => stmt.index_for_column_name(field_name), + PrivateSqliteRow::Duplicated { column_names, .. } => column_names + .iter() + .position(|n| n.as_ref().map(|s| s as &str) == Some(field_name)), + } + } +} + +#[allow(missing_debug_implementations)] +pub struct SqliteField<'stmt, 'query> { + pub(super) row: Ref<'stmt, PrivateSqliteRow<'stmt, 'query>>, + pub(super) col_idx: i32, +} + +impl<'stmt, 'query> Field<'stmt, Sqlite> for SqliteField<'stmt, 'query> { + fn field_name(&self) -> Option<&str> { + match &*self.row { + PrivateSqliteRow::Direct(stmt) => stmt.field_name(self.col_idx), + PrivateSqliteRow::Duplicated { column_names, .. } => column_names + .get(self.col_idx as usize) + .and_then(|t| t.as_ref().map(|n| n as &str)), + } + } + + fn is_null(&self) -> bool { + self.value().is_none() + } + + fn value(&self) -> Option<::RawValue<'_>> { + SqliteValue::new(Ref::clone(&self.row), self.col_idx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fun_with_row_iters() { + crate::table! { + #[allow(unused_parens)] + users(id) { + id -> Integer, + name -> Text, + } + } + + use crate::connection::LoadConnection; + use crate::deserialize::{FromSql, FromSqlRow}; + use crate::prelude::*; + use crate::row::{Field, Row}; + use crate::sql_types; + + let conn = &mut crate::test_helpers::connection(); + + crate::sql_query("CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);") + .execute(conn) + .unwrap(); + + crate::insert_into(users::table) + .values(vec![ + (users::id.eq(1), users::name.eq("Sean")), + (users::id.eq(2), users::name.eq("Tess")), + ]) + .execute(conn) + .unwrap(); + + let query = users::table.select((users::id, users::name)); + + let expected = vec![(1, String::from("Sean")), (2, String::from("Tess"))]; + + let row_iter = conn.load(query).unwrap(); + for (row, expected) in row_iter.zip(&expected) { + let row = row.unwrap(); + + let deserialized = <(i32, String) as FromSqlRow< + (sql_types::Integer, sql_types::Text), + _, + >>::build_from_row(&row) + .unwrap(); + + assert_eq!(&deserialized, expected); + } + + { + let collected_rows = conn.load(query).unwrap().collect::>(); + + for (row, expected) in collected_rows.iter().zip(&expected) { + let deserialized = row + .as_ref() + .map(|row| { + <(i32, String) as FromSqlRow< + (sql_types::Integer, sql_types::Text), + _, + >>::build_from_row(row).unwrap() + }) + .unwrap(); + + assert_eq!(&deserialized, expected); + } + } + + let mut row_iter = conn.load(query).unwrap(); + + let first_row = row_iter.next().unwrap().unwrap(); + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let first_values = (first_fields.0.value(), first_fields.1.value()); + + assert!(row_iter.next().unwrap().is_err()); + std::mem::drop(first_values); + assert!(row_iter.next().unwrap().is_err()); + std::mem::drop(first_fields); + + let second_row = row_iter.next().unwrap().unwrap(); + let second_fields = (second_row.get(0).unwrap(), second_row.get(1).unwrap()); + let second_values = (second_fields.0.value(), second_fields.1.value()); + + assert!(row_iter.next().unwrap().is_err()); + std::mem::drop(second_values); + assert!(row_iter.next().unwrap().is_err()); + std::mem::drop(second_fields); + + assert!(row_iter.next().is_none()); + + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let second_fields = (second_row.get(0).unwrap(), second_row.get(1).unwrap()); + + let first_values = (first_fields.0.value(), first_fields.1.value()); + let second_values = (second_fields.0.value(), second_fields.1.value()); + + assert_eq!( + >::from_nullable_sql(first_values.0) + .unwrap(), + expected[0].0 + ); + assert_eq!( + >::from_nullable_sql(first_values.1) + .unwrap(), + expected[0].1 + ); + + assert_eq!( + >::from_nullable_sql(second_values.0) + .unwrap(), + expected[1].0 + ); + assert_eq!( + >::from_nullable_sql(second_values.1) + .unwrap(), + expected[1].1 + ); + + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let first_values = (first_fields.0.value(), first_fields.1.value()); + + assert_eq!( + >::from_nullable_sql(first_values.0) + .unwrap(), + expected[0].0 + ); + assert_eq!( + >::from_nullable_sql(first_values.1) + .unwrap(), + expected[0].1 + ); + } + + #[cfg(feature = "returning_clauses_for_sqlite_3_35")] + crate::define_sql_function! {fn sleep(a: diesel::sql_types::Integer) -> diesel::sql_types::Integer} + + #[test] + #[cfg(feature = "returning_clauses_for_sqlite_3_35")] + fn parallel_iter_with_error() { + use crate::connection::Connection; + use crate::connection::LoadConnection; + use crate::connection::SimpleConnection; + use crate::expression_methods::ExpressionMethods; + use crate::SqliteConnection; + use std::sync::{Arc, Barrier}; + use std::time::Duration; + + let temp_dir = tempfile::tempdir().unwrap(); + let db_path = format!("{}/test.db", temp_dir.path().display()); + let mut conn1 = SqliteConnection::establish(&db_path).unwrap(); + let mut conn2 = SqliteConnection::establish(&db_path).unwrap(); + + crate::table! { + users { + id -> Integer, + name -> Text, + } + } + + conn1 + .batch_execute("CREATE TABLE users(id INTEGER NOT NULL PRIMARY KEY, name TEXT)") + .unwrap(); + + let barrier = Arc::new(Barrier::new(2)); + let barrier2 = barrier.clone(); + + // we unblock the main thread from the sleep function + sleep_utils::register_impl(&mut conn2, move |a: i32| { + barrier.wait(); + std::thread::sleep(Duration::from_secs(a as u64)); + a + }) + .unwrap(); + + // spawn a background thread that locks the database file + let handle = std::thread::spawn(move || { + use crate::query_dsl::RunQueryDsl; + + conn2 + .immediate_transaction(|conn| diesel::select(sleep(1)).execute(conn)) + .unwrap(); + }); + barrier2.wait(); + + // execute some action that also requires a lock + let mut iter = conn1 + .load( + diesel::insert_into(users::table) + .values((users::id.eq(1), users::name.eq("John"))) + .returning(users::id), + ) + .unwrap(); + + // get the first iterator result, that should return the lock error + let n = iter.next().unwrap(); + assert!(n.is_err()); + + // check that the iterator is now empty + let n = iter.next(); + assert!(n.is_none()); + + // join the background thread + handle.join().unwrap(); + } +} diff --git a/diesel-wasm-sqlite/src/connection/serialized_database.rs b/diesel-wasm-sqlite/src/connection/serialized_database.rs new file mode 100644 index 000000000..28a98a286 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/serialized_database.rs @@ -0,0 +1,45 @@ +#![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, + len: usize, +} + +impl SerializedDatabase { + /// Creates a new `SerializedDatabase` with the given data pointer and length. + /// + /// SAFETY: The data pointer needs to be returned by sqlite + /// and the length must match the underlying buffer pointer + pub(crate) unsafe fn new(data: *mut u8, len: usize) -> Self { + Self { data, len } + } + + /// Returns a slice of the serialized database. + pub fn as_slice(&self) -> &[u8] { + // The pointer is never null because we don't pass the NO_COPY flag + unsafe { std::slice::from_raw_parts(self.data, self.len) } + } +} + +impl Deref for SerializedDatabase { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl Drop for SerializedDatabase { + /// Deallocates the memory of the serialized database when it goes out of scope. + fn drop(&mut self) { + unsafe { + ffi::sqlite3_free(self.data as _); + } + } +} diff --git a/diesel-wasm-sqlite/src/connection/sqlite_value.rs b/diesel-wasm-sqlite/src/connection/sqlite_value.rs new file mode 100644 index 000000000..7210d104b --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/sqlite_value.rs @@ -0,0 +1,172 @@ +#![allow(unsafe_code)] // ffi calls +extern crate libsqlite3_sys as ffi; + +use std::cell::Ref; +use std::ptr::NonNull; +use std::{slice, str}; + +use crate::sqlite::SqliteType; + +use super::owned_row::OwnedSqliteRow; +use super::row::PrivateSqliteRow; + +/// Raw sqlite value as received from the database +/// +/// Use existing `FromSql` implementations to convert this into +/// rust values +#[allow(missing_debug_implementations, missing_copy_implementations)] +pub struct SqliteValue<'row, 'stmt, 'query> { + // This field exists to ensure that nobody + // can modify the underlying row while we are + // holding a reference to some row value here + _row: Option>>, + // we extract the raw value pointer as part of the constructor + // to safe the match statements for each method + // According to benchmarks this leads to a ~20-30% speedup + // + // This is sound as long as nobody calls `stmt.step()` + // while holding this value. We ensure this by including + // a reference to the row above. + value: NonNull, +} + +#[derive(Debug)] +#[repr(transparent)] +pub(super) struct OwnedSqliteValue { + pub(super) value: NonNull, +} + +impl Drop for OwnedSqliteValue { + fn drop(&mut self) { + unsafe { ffi::sqlite3_value_free(self.value.as_ptr()) } + } +} + +// Unsafe Send impl safe since sqlite3_value is built with sqlite3_value_dup +// see https://www.sqlite.org/c3ref/value.html +unsafe impl Send for OwnedSqliteValue {} + +impl<'row, 'stmt, 'query> SqliteValue<'row, 'stmt, 'query> { + pub(super) fn new( + row: Ref<'row, PrivateSqliteRow<'stmt, 'query>>, + col_idx: i32, + ) -> Option> { + let value = match &*row { + PrivateSqliteRow::Direct(stmt) => stmt.column_value(col_idx)?, + PrivateSqliteRow::Duplicated { values, .. } => { + values.get(col_idx as usize).and_then(|v| v.as_ref())?.value + } + }; + + let ret = Self { + _row: Some(row), + value, + }; + if ret.value_type().is_none() { + None + } else { + Some(ret) + } + } + + pub(super) fn from_owned_row( + row: &'row OwnedSqliteRow, + col_idx: i32, + ) -> Option> { + let value = row + .values + .get(col_idx as usize) + .and_then(|v| v.as_ref())? + .value; + let ret = Self { _row: None, value }; + if ret.value_type().is_none() { + None + } else { + Some(ret) + } + } + + pub(crate) fn parse_string<'value, R>(&'value self, f: impl FnOnce(&'value str) -> R) -> R { + let s = unsafe { + let ptr = ffi::sqlite3_value_text(self.value.as_ptr()); + let len = ffi::sqlite3_value_bytes(self.value.as_ptr()); + let bytes = slice::from_raw_parts(ptr, len as usize); + // The string is guaranteed to be utf8 according to + // https://www.sqlite.org/c3ref/value_blob.html + str::from_utf8_unchecked(bytes) + }; + f(s) + } + + pub(crate) fn read_text(&self) -> &str { + self.parse_string(|s| s) + } + + pub(crate) fn read_blob(&self) -> &[u8] { + unsafe { + let ptr = ffi::sqlite3_value_blob(self.value.as_ptr()); + let len = ffi::sqlite3_value_bytes(self.value.as_ptr()); + if len == 0 { + // rusts std-lib has an debug_assert that prevents creating + // slices without elements from a pointer + &[] + } else { + slice::from_raw_parts(ptr as *const u8, len as usize) + } + } + } + + pub(crate) fn read_integer(&self) -> i32 { + unsafe { ffi::sqlite3_value_int(self.value.as_ptr()) } + } + + pub(crate) fn read_long(&self) -> i64 { + unsafe { ffi::sqlite3_value_int64(self.value.as_ptr()) } + } + + pub(crate) fn read_double(&self) -> f64 { + unsafe { ffi::sqlite3_value_double(self.value.as_ptr()) } + } + + /// Get the type of the value as returned by sqlite + pub fn value_type(&self) -> Option { + let tpe = unsafe { ffi::sqlite3_value_type(self.value.as_ptr()) }; + match tpe { + ffi::SQLITE_TEXT => Some(SqliteType::Text), + ffi::SQLITE_INTEGER => Some(SqliteType::Long), + ffi::SQLITE_FLOAT => Some(SqliteType::Double), + ffi::SQLITE_BLOB => Some(SqliteType::Binary), + ffi::SQLITE_NULL => None, + _ => unreachable!( + "Sqlite's documentation state that this case ({}) is not reachable. \ + If you ever see this error message please open an issue at \ + https://github.com/diesel-rs/diesel.", + tpe + ), + } + } +} + +impl OwnedSqliteValue { + pub(super) fn copy_from_ptr(ptr: NonNull) -> Option { + let tpe = unsafe { ffi::sqlite3_value_type(ptr.as_ptr()) }; + if ffi::SQLITE_NULL == tpe { + return None; + } + let value = unsafe { ffi::sqlite3_value_dup(ptr.as_ptr()) }; + Some(Self { + value: NonNull::new(value)?, + }) + } + + pub(super) fn duplicate(&self) -> OwnedSqliteValue { + // self.value is a `NonNull` ptr so this cannot be null + let value = unsafe { ffi::sqlite3_value_dup(self.value.as_ptr()) }; + let value = NonNull::new(value).expect( + "Sqlite documentation states this returns only null if value is null \ + or OOM. If you ever see this panic message please open an issue at \ + https://github.com/diesel-rs/diesel.", + ); + OwnedSqliteValue { value } + } +} diff --git a/diesel-wasm-sqlite/src/connection/statement_iterator.rs b/diesel-wasm-sqlite/src/connection/statement_iterator.rs new file mode 100644 index 000000000..393ec9e47 --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/statement_iterator.rs @@ -0,0 +1,172 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::row::{PrivateSqliteRow, SqliteRow}; +use super::stmt::StatementUse; +use crate::result::QueryResult; + +#[allow(missing_debug_implementations)] +pub struct StatementIterator<'stmt, 'query> { + inner: PrivateStatementIterator<'stmt, 'query>, + column_names: Option]>>, + field_count: usize, +} + +impl<'stmt, 'query> StatementIterator<'stmt, 'query> { + #[cold] + #[allow(unsafe_code)] // call to unsafe function + fn handle_duplicated_row_case( + outer_last_row: &mut Rc>>, + column_names: &mut Option]>>, + field_count: usize, + ) -> Option>> { + // We don't own the statement. There is another existing reference, likely because + // a user stored the row in some long time container before calling next another time + // In this case we copy out the current values into a temporary store and advance + // the statement iterator internally afterwards + let last_row = { + let mut last_row = match outer_last_row.try_borrow_mut() { + Ok(o) => o, + Err(_e) => { + return Some(Err(crate::result::Error::DeserializationError( + "Failed to reborrow row. Try to release any `SqliteField` or `SqliteValue` \ + that exists at this point" + .into(), + ))); + } + }; + let last_row = &mut *last_row; + let duplicated = last_row.duplicate(column_names); + std::mem::replace(last_row, duplicated) + }; + if let PrivateSqliteRow::Direct(mut stmt) = last_row { + let res = unsafe { + // This is actually safe here as we've already + // performed one step. For the first step we would have + // used `PrivateStatementIterator::NotStarted` where we don't + // have access to `PrivateSqliteRow` at all + stmt.step(false) + }; + *outer_last_row = Rc::new(RefCell::new(PrivateSqliteRow::Direct(stmt))); + match res { + Err(e) => Some(Err(e)), + Ok(false) => None, + Ok(true) => Some(Ok(SqliteRow { + inner: Rc::clone(outer_last_row), + field_count, + })), + } + } else { + // any other state than `PrivateSqliteRow::Direct` is invalid here + // and should not happen. If this ever happens this is a logic error + // in the code above + unreachable!( + "You've reached an impossible internal state. \ + If you ever see this error message please open \ + an issue at https://github.com/diesel-rs/diesel \ + providing example code how to trigger this error." + ) + } + } +} + +enum PrivateStatementIterator<'stmt, 'query> { + NotStarted(Option>), + Started(Rc>>), +} + +impl<'stmt, 'query> StatementIterator<'stmt, 'query> { + pub fn new(stmt: StatementUse<'stmt, 'query>) -> StatementIterator<'stmt, 'query> { + Self { + inner: PrivateStatementIterator::NotStarted(Some(stmt)), + column_names: None, + field_count: 0, + } + } +} + +impl<'stmt, 'query> Iterator for StatementIterator<'stmt, 'query> { + type Item = QueryResult>; + + #[allow(unsafe_code)] // call to unsafe function + fn next(&mut self) -> Option { + use PrivateStatementIterator::{NotStarted, Started}; + match &mut self.inner { + NotStarted(ref mut stmt @ Some(_)) => { + let mut stmt = stmt + .take() + .expect("It must be there because we checked that above"); + let step = unsafe { + // This is safe as we pass `first_step = true` to reset the cached column names + stmt.step(true) + }; + match step { + Err(e) => Some(Err(e)), + Ok(false) => None, + Ok(true) => { + let field_count = stmt.column_count() as usize; + self.field_count = field_count; + let inner = Rc::new(RefCell::new(PrivateSqliteRow::Direct(stmt))); + self.inner = Started(inner.clone()); + Some(Ok(SqliteRow { inner, field_count })) + } + } + } + Started(ref mut last_row) => { + // There was already at least one iteration step + // We check here if the caller already released the row value or not + // by checking if our Rc owns the data or not + if let Some(last_row_ref) = Rc::get_mut(last_row) { + // We own the statement, there is no other reference here. + // This means we don't need to copy out values from the sqlite provided + // datastructures for now + // We don't need to use the runtime borrowing system of the RefCell here + // as we have a mutable reference, so all of this below is checked at compile time + if let PrivateSqliteRow::Direct(ref mut stmt) = last_row_ref.get_mut() { + let step = unsafe { + // This is actually safe here as we've already + // performed one step. For the first step we would have + // used `PrivateStatementIterator::NotStarted` where we don't + // have access to `PrivateSqliteRow` at all + + stmt.step(false) + }; + match step { + Err(e) => Some(Err(e)), + Ok(false) => None, + Ok(true) => { + let field_count = self.field_count; + Some(Ok(SqliteRow { + inner: Rc::clone(last_row), + field_count, + })) + } + } + } else { + // any other state than `PrivateSqliteRow::Direct` is invalid here + // and should not happen. If this ever happens this is a logic error + // in the code above + unreachable!( + "You've reached an impossible internal state. \ + If you ever see this error message please open \ + an issue at https://github.com/diesel-rs/diesel \ + providing example code how to trigger this error." + ) + } + } else { + Self::handle_duplicated_row_case( + last_row, + &mut self.column_names, + self.field_count, + ) + } + } + NotStarted(_s) => { + // we likely got an error while executing the other + // `NotStarted` branch above. In this case we just want to stop + // iterating here + None + } + } + } +} diff --git a/diesel-wasm-sqlite/src/connection/stmt.rs b/diesel-wasm-sqlite/src/connection/stmt.rs new file mode 100644 index 000000000..92b12465d --- /dev/null +++ b/diesel-wasm-sqlite/src/connection/stmt.rs @@ -0,0 +1,540 @@ +#![allow(unsafe_code)] // fii code +use super::bind_collector::{InternalSqliteBindValue, SqliteBindCollector}; +use super::raw::RawConnection; +use super::sqlite_value::OwnedSqliteValue; +use crate::connection::statement_cache::{MaybeCached, PrepareForCache}; +use crate::connection::Instrumentation; +use crate::query_builder::{QueryFragment, QueryId}; +use crate::result::Error::DatabaseError; +use crate::result::*; +use crate::sqlite::{Sqlite, SqliteType}; +use libsqlite3_sys as ffi; +use std::cell::OnceCell; +use std::ffi::{CStr, CString}; +use std::io::{stderr, Write}; +use std::os::raw as libc; +use std::ptr::{self, NonNull}; + +pub(super) struct Statement { + inner_statement: NonNull, +} + +impl Statement { + pub(super) fn prepare( + raw_connection: &RawConnection, + sql: &str, + is_cached: PrepareForCache, + ) -> QueryResult { + let mut stmt = ptr::null_mut(); + let mut unused_portion = ptr::null(); + // the cast for `ffi::SQLITE_PREPARE_PERSISTENT` is required for old libsqlite3-sys versions + #[allow(clippy::unnecessary_cast)] + let prepare_result = unsafe { + ffi::sqlite3_prepare_v3( + raw_connection.internal_connection.as_ptr(), + CString::new(sql)?.as_ptr(), + sql.len() as libc::c_int, + if matches!(is_cached, PrepareForCache::Yes) { + ffi::SQLITE_PREPARE_PERSISTENT as u32 + } else { + 0 + }, + &mut stmt, + &mut unused_portion, + ) + }; + + ensure_sqlite_ok(prepare_result, raw_connection.internal_connection.as_ptr()).map(|_| { + Statement { + inner_statement: unsafe { NonNull::new_unchecked(stmt) }, + } + }) + } + + // The caller of this function has to ensure that: + // * Any buffer provided as `SqliteBindValue::BorrowedBinary`, `SqliteBindValue::Binary` + // `SqliteBindValue::String` or `SqliteBindValue::BorrowedString` is valid + // till either a new value is bound to the same parameter or the underlying + // prepared statement is dropped. + unsafe fn bind( + &mut self, + tpe: SqliteType, + value: InternalSqliteBindValue<'_>, + bind_index: i32, + ) -> QueryResult>> { + let mut ret_ptr = None; + let result = match (tpe, value) { + (_, InternalSqliteBindValue::Null) => { + ffi::sqlite3_bind_null(self.inner_statement.as_ptr(), bind_index) + } + (SqliteType::Binary, InternalSqliteBindValue::BorrowedBinary(bytes)) => { + ffi::sqlite3_bind_blob( + self.inner_statement.as_ptr(), + bind_index, + bytes.as_ptr() as *const libc::c_void, + bytes.len() as libc::c_int, + ffi::SQLITE_STATIC(), + ) + } + (SqliteType::Binary, InternalSqliteBindValue::Binary(mut bytes)) => { + let len = bytes.len(); + // We need a separate pointer here to pass it to sqlite + // as the returned pointer is a pointer to a dyn sized **slice** + // and not the pointer to the first element of the slice + let ptr = bytes.as_mut_ptr(); + ret_ptr = NonNull::new(Box::into_raw(bytes)); + ffi::sqlite3_bind_blob( + self.inner_statement.as_ptr(), + bind_index, + ptr as *const libc::c_void, + len as libc::c_int, + ffi::SQLITE_STATIC(), + ) + } + (SqliteType::Text, InternalSqliteBindValue::BorrowedString(bytes)) => { + ffi::sqlite3_bind_text( + self.inner_statement.as_ptr(), + bind_index, + bytes.as_ptr() as *const libc::c_char, + bytes.len() as libc::c_int, + ffi::SQLITE_STATIC(), + ) + } + (SqliteType::Text, InternalSqliteBindValue::String(bytes)) => { + let mut bytes = Box::<[u8]>::from(bytes); + let len = bytes.len(); + // We need a separate pointer here to pass it to sqlite + // as the returned pointer is a pointer to a dyn sized **slice** + // and not the pointer to the first element of the slice + let ptr = bytes.as_mut_ptr(); + ret_ptr = NonNull::new(Box::into_raw(bytes)); + ffi::sqlite3_bind_text( + self.inner_statement.as_ptr(), + bind_index, + ptr as *const libc::c_char, + len as libc::c_int, + ffi::SQLITE_STATIC(), + ) + } + (SqliteType::Float, InternalSqliteBindValue::F64(value)) + | (SqliteType::Double, InternalSqliteBindValue::F64(value)) => { + ffi::sqlite3_bind_double( + self.inner_statement.as_ptr(), + bind_index, + value as libc::c_double, + ) + } + (SqliteType::SmallInt, InternalSqliteBindValue::I32(value)) + | (SqliteType::Integer, InternalSqliteBindValue::I32(value)) => { + ffi::sqlite3_bind_int(self.inner_statement.as_ptr(), bind_index, value) + } + (SqliteType::Long, InternalSqliteBindValue::I64(value)) => { + ffi::sqlite3_bind_int64(self.inner_statement.as_ptr(), bind_index, value) + } + (t, b) => { + return Err(Error::SerializationError( + format!("Type mismatch: Expected {t:?}, got {b}").into(), + )) + } + }; + match ensure_sqlite_ok(result, self.raw_connection()) { + Ok(()) => Ok(ret_ptr), + Err(e) => { + if let Some(ptr) = ret_ptr { + // This is a `NonNul` ptr so it cannot be null + // It points to a slice internally as we did not apply + // any cast above. + std::mem::drop(Box::from_raw(ptr.as_ptr())) + } + Err(e) + } + } + } + + fn reset(&mut self) { + unsafe { ffi::sqlite3_reset(self.inner_statement.as_ptr()) }; + } + + fn raw_connection(&self) -> *mut ffi::sqlite3 { + unsafe { ffi::sqlite3_db_handle(self.inner_statement.as_ptr()) } + } +} + +pub(super) fn ensure_sqlite_ok( + code: libc::c_int, + raw_connection: *mut ffi::sqlite3, +) -> QueryResult<()> { + if code == ffi::SQLITE_OK { + Ok(()) + } else { + Err(last_error(raw_connection)) + } +} + +fn last_error(raw_connection: *mut ffi::sqlite3) -> Error { + let error_message = last_error_message(raw_connection); + let error_information = Box::new(error_message); + let error_kind = match last_error_code(raw_connection) { + ffi::SQLITE_CONSTRAINT_UNIQUE | ffi::SQLITE_CONSTRAINT_PRIMARYKEY => { + DatabaseErrorKind::UniqueViolation + } + ffi::SQLITE_CONSTRAINT_FOREIGNKEY => DatabaseErrorKind::ForeignKeyViolation, + ffi::SQLITE_CONSTRAINT_NOTNULL => DatabaseErrorKind::NotNullViolation, + ffi::SQLITE_CONSTRAINT_CHECK => DatabaseErrorKind::CheckViolation, + _ => DatabaseErrorKind::Unknown, + }; + DatabaseError(error_kind, error_information) +} + +fn last_error_message(conn: *mut ffi::sqlite3) -> String { + let c_str = unsafe { CStr::from_ptr(ffi::sqlite3_errmsg(conn)) }; + c_str.to_string_lossy().into_owned() +} + +fn last_error_code(conn: *mut ffi::sqlite3) -> libc::c_int { + unsafe { ffi::sqlite3_extended_errcode(conn) } +} + +impl Drop for Statement { + fn drop(&mut self) { + use std::thread::panicking; + + let raw_connection = self.raw_connection(); + let finalize_result = unsafe { ffi::sqlite3_finalize(self.inner_statement.as_ptr()) }; + if let Err(e) = ensure_sqlite_ok(finalize_result, raw_connection) { + if panicking() { + write!( + stderr(), + "Error finalizing SQLite prepared statement: {e:?}" + ) + .expect("Error writing to `stderr`"); + } else { + panic!("Error finalizing SQLite prepared statement: {:?}", e); + } + } + } +} + +// A warning for future editors: +// Changing this code to something "simpler" may +// introduce undefined behaviour. Make sure you read +// the following discussions for details about +// the current version: +// +// * https://github.com/weiznich/diesel/pull/7 +// * https://users.rust-lang.org/t/code-review-for-unsafe-code-in-diesel/66798/ +// * https://github.com/rust-lang/unsafe-code-guidelines/issues/194 +struct BoundStatement<'stmt, 'query> { + statement: MaybeCached<'stmt, Statement>, + // we need to store the query here to ensure no one does + // drop it till the end of the statement + // We use a boxed queryfragment here just to erase the + // generic type, we use NonNull to communicate + // that this is a shared buffer + query: Option + 'query>>, + // we need to store any owned bind values separately, as they are not + // contained in the query itself. We use NonNull to + // communicate that this is a shared buffer + binds_to_free: Vec<(i32, Option>)>, + instrumentation: &'stmt mut dyn Instrumentation, + has_error: bool, +} + +impl<'stmt, 'query> BoundStatement<'stmt, 'query> { + fn bind( + statement: MaybeCached<'stmt, Statement>, + query: T, + instrumentation: &'stmt mut dyn Instrumentation, + ) -> QueryResult> + where + T: QueryFragment + QueryId + 'query, + { + // Don't use a trait object here to prevent using a virtual function call + // For sqlite this can introduce a measurable overhead + // Query is boxed here to make sure it won't move in memory anymore, so any bind + // it could output would stay valid. + let query = Box::new(query); + + let mut bind_collector = SqliteBindCollector::new(); + query.collect_binds(&mut bind_collector, &mut (), &Sqlite)?; + let SqliteBindCollector { binds } = bind_collector; + + let mut ret = BoundStatement { + statement, + query: None, + binds_to_free: Vec::new(), + instrumentation, + has_error: false, + }; + + ret.bind_buffers(binds)?; + + let query = query as Box + 'query>; + ret.query = NonNull::new(Box::into_raw(query)); + + Ok(ret) + } + + // This is a separated function so that + // not the whole constructor is generic over the query type T. + // This hopefully prevents binary bloat. + fn bind_buffers( + &mut self, + binds: Vec<(InternalSqliteBindValue<'_>, SqliteType)>, + ) -> QueryResult<()> { + // It is useful to preallocate `binds_to_free` because it + // - Guarantees that pushing inside it cannot panic, which guarantees the `Drop` + // impl of `BoundStatement` will always re-`bind` as needed + // - Avoids reallocations + self.binds_to_free.reserve( + binds + .iter() + .filter(|&(b, _)| { + matches!( + b, + InternalSqliteBindValue::BorrowedBinary(_) + | InternalSqliteBindValue::BorrowedString(_) + | InternalSqliteBindValue::String(_) + | InternalSqliteBindValue::Binary(_) + ) + }) + .count(), + ); + for (bind_idx, (bind, tpe)) in (1..).zip(binds) { + let is_borrowed_bind = matches!( + bind, + InternalSqliteBindValue::BorrowedString(_) + | InternalSqliteBindValue::BorrowedBinary(_) + ); + + // It's safe to call bind here as: + // * The type and value matches + // * We ensure that corresponding buffers lives long enough below + // * The statement is not used yet by `step` or anything else + let res = unsafe { self.statement.bind(tpe, bind, bind_idx) }?; + + // it's important to push these only after + // the call to bind succeeded, otherwise we might attempt to + // call bind to an non-existing bind position in + // the destructor + if let Some(ptr) = res { + // Store the id + pointer for a owned bind + // as we must unbind and free them on drop + self.binds_to_free.push((bind_idx, Some(ptr))); + } else if is_borrowed_bind { + // Store the id's of borrowed binds to unbind them on drop + self.binds_to_free.push((bind_idx, None)); + } + } + Ok(()) + } + + fn finish_query_with_error(mut self, e: &Error) { + self.has_error = true; + if let Some(q) = self.query { + // it's safe to get a reference from this ptr as it's guaranteed to not be null + let q = unsafe { q.as_ref() }; + self.instrumentation.on_connection_event( + crate::connection::InstrumentationEvent::FinishQuery { + query: &crate::debug_query(&q), + error: Some(e), + }, + ); + } + } +} + +impl<'stmt, 'query> Drop for BoundStatement<'stmt, 'query> { + fn drop(&mut self) { + // First reset the statement, otherwise the bind calls + // below will fails + self.statement.reset(); + + for (idx, buffer) in std::mem::take(&mut self.binds_to_free) { + unsafe { + // It's always safe to bind null values, as there is no buffer that needs to outlife something + self.statement + .bind(SqliteType::Text, InternalSqliteBindValue::Null, idx) + .expect( + "Binding a null value should never fail. \ + If you ever see this error message please open \ + an issue at diesels issue tracker containing \ + code how to trigger this message.", + ); + } + + if let Some(buffer) = buffer { + unsafe { + // Constructing the `Box` here is safe as we + // got the pointer from a box + it is guaranteed to be not null. + std::mem::drop(Box::from_raw(buffer.as_ptr())); + } + } + } + + if let Some(query) = self.query { + let query = unsafe { + // Constructing the `Box` here is safe as we + // got the pointer from a box + it is guaranteed to be not null. + Box::from_raw(query.as_ptr()) + }; + if !self.has_error { + self.instrumentation.on_connection_event( + crate::connection::InstrumentationEvent::FinishQuery { + query: &crate::debug_query(&query), + error: None, + }, + ); + } + std::mem::drop(query); + self.query = None; + } + } +} + +#[allow(missing_debug_implementations)] +pub struct StatementUse<'stmt, 'query> { + statement: BoundStatement<'stmt, 'query>, + column_names: OnceCell>, +} + +impl<'stmt, 'query> StatementUse<'stmt, 'query> { + pub(super) fn bind( + statement: MaybeCached<'stmt, Statement>, + query: T, + instrumentation: &'stmt mut dyn Instrumentation, + ) -> QueryResult> + where + T: QueryFragment + QueryId + 'query, + { + Ok(Self { + statement: BoundStatement::bind(statement, query, instrumentation)?, + column_names: OnceCell::new(), + }) + } + + pub(super) fn run(mut self) -> QueryResult<()> { + let r = unsafe { + // This is safe as we pass `first_step = true` + // and we consume the statement so nobody could + // access the columns later on anyway. + self.step(true).map(|_| ()) + }; + if let Err(ref e) = r { + self.statement.finish_query_with_error(e); + } + r + } + + // This function is marked as unsafe incorrectly passing `false` to `first_step` + // for a first call to this function could cause access to freed memory via + // the cached column names. + // + // It's always safe to call this function with `first_step = true` as this removes + // the cached column names + pub(super) unsafe fn step(&mut self, first_step: bool) -> QueryResult { + let res = match ffi::sqlite3_step(self.statement.statement.inner_statement.as_ptr()) { + ffi::SQLITE_DONE => Ok(false), + ffi::SQLITE_ROW => Ok(true), + _ => Err(last_error(self.statement.statement.raw_connection())), + }; + if first_step { + self.column_names = OnceCell::new(); + } + res + } + + // The returned string pointer is valid until either the prepared statement is + // destroyed by sqlite3_finalize() or until the statement is automatically + // reprepared by the first call to sqlite3_step() for a particular run or + // until the next call to sqlite3_column_name() or sqlite3_column_name16() + // on the same column. + // + // https://sqlite.org/c3ref/column_name.html + // + // Note: This function is marked as unsafe, as calling it can invalidate + // other existing column name pointers on the same column. To prevent that, + // it should maximally be called once per column at all. + unsafe fn column_name(&self, idx: i32) -> *const str { + let name = { + let column_name = + ffi::sqlite3_column_name(self.statement.statement.inner_statement.as_ptr(), idx); + assert!( + !column_name.is_null(), + "The Sqlite documentation states that it only returns a \ + null pointer here if we are in a OOM condition." + ); + CStr::from_ptr(column_name) + }; + name.to_str().expect( + "The Sqlite documentation states that this is UTF8. \ + If you see this error message something has gone \ + horribly wrong. Please open an issue at the \ + diesel repository.", + ) as *const str + } + + pub(super) fn column_count(&self) -> i32 { + unsafe { ffi::sqlite3_column_count(self.statement.statement.inner_statement.as_ptr()) } + } + + pub(super) fn index_for_column_name(&mut self, field_name: &str) -> Option { + (0..self.column_count()) + .find(|idx| self.field_name(*idx) == Some(field_name)) + .map(|v| v as usize) + } + + pub(super) fn field_name(&self, idx: i32) -> Option<&str> { + let column_names = self.column_names.get_or_init(|| { + let count = self.column_count(); + (0..count) + .map(|idx| unsafe { + // By initializing the whole vec at once we ensure that + // we really call this only once. + self.column_name(idx) + }) + .collect() + }); + + column_names + .get(idx as usize) + .and_then(|c| unsafe { c.as_ref() }) + } + + pub(super) fn copy_value(&self, idx: i32) -> Option { + OwnedSqliteValue::copy_from_ptr(self.column_value(idx)?) + } + + pub(super) fn column_value(&self, idx: i32) -> Option> { + let ptr = unsafe { + ffi::sqlite3_column_value(self.statement.statement.inner_statement.as_ptr(), idx) + }; + NonNull::new(ptr) + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + use crate::sql_types::Text; + + // this is a regression test for + // https://github.com/diesel-rs/diesel/issues/3558 + #[test] + fn check_out_of_bounds_bind_does_not_panic_on_drop() { + let mut conn = SqliteConnection::establish(":memory:").unwrap(); + + let e = crate::sql_query("SELECT '?'") + .bind::("foo") + .execute(&mut conn); + + assert!(e.is_err()); + let e = e.unwrap_err(); + if let crate::result::Error::DatabaseError(crate::result::DatabaseErrorKind::Unknown, m) = e + { + assert_eq!(m.message(), "column index out of range"); + } else { + panic!("Wrong error returned"); + } + } +} diff --git a/diesel-wasm-sqlite/src/ffi.rs b/diesel-wasm-sqlite/src/ffi.rs index be6edffd0..c6c62da27 100644 --- a/diesel-wasm-sqlite/src/ffi.rs +++ b/diesel-wasm-sqlite/src/ffi.rs @@ -1,5 +1,6 @@ use wasm_bindgen::{prelude::*, JsValue}; +/* /// Simple Connection #[wasm_bindgen(module = "/src/package.js")] extern "C" { @@ -9,25 +10,63 @@ extern "C" { #[wasm_bindgen(catch)] pub async fn establish(database_url: &str) -> Result; } +*/ + +// WASM is ran in the browser `main thread`. Tokio is only a single-threaded runtime. +// We need SQLite available globally, so this should be ok until we get threads with WASI or +// something. At which point we can (hopefully) use multi-threaded async runtime to block the +// thread and get SQLite. +unsafe impl Send for SQLite {} +unsafe impl Sync for SQLite {} /// Direct Shim for wa-sqlite #[wasm_bindgen(module = "/src/package.js")] extern "C" { - #[wasm_bindgen] - pub fn sqlite3_result_text(context: i32, value: String); + pub type SQLite; + + #[wasm_bindgen(constructor)] + pub fn new(module: JsValue) -> SQLite; + + #[wasm_bindgen(static_method_of = SQLite)] + pub async fn wasm_module() -> JsValue; + + #[wasm_bindgen(method)] + pub fn result_text(this: &SQLite, context: i32, value: String); + + #[wasm_bindgen(method)] + pub fn result_int(this: &SQLite, context: i32, value: i32); - #[wasm_bindgen] - pub fn sqlite3_result_int(context: i32, value: i32); + #[wasm_bindgen(method)] + pub fn result_int64(this: &SQLite, context: i32, value: i64); - #[wasm_bindgen] - pub fn sqlite3_result_int64(context: i32, value: i64); + #[wasm_bindgen(method)] + pub fn result_double(this: &SQLite, context: i32, value: f64); - #[wasm_bindgen] - pub fn sqlite3_result_double(context: i32, value: f64); + #[wasm_bindgen(method)] + pub fn result_blob(this: &SQLite, context: i32, value: Vec); - #[wasm_bindgen] - pub fn sqlite3_result_blob(context: i32, value: Vec); + #[wasm_bindgen(method)] + pub fn result_null(this: &SQLite, context: i32); + + #[wasm_bindgen(method, catch)] + pub async fn open_v2( + this: &SQLite, + database_url: &str, + iflags: Option, + ) -> Result; + + #[wasm_bindgen(method, catch)] + pub async fn exec(this: &SQLite, database: &JsValue, query: &str) -> Result<(), JsValue>; + + #[wasm_bindgen(method)] + pub fn changes(this: &SQLite, database: &JsValue) -> usize; + + #[wasm_bindgen(method, catch)] + pub fn batch_execute(this: &SQLite, database: &JsValue, query: &str) -> Result<(), JsValue>; +} - #[wasm_bindgen] - pub fn sqlite3_result_null(context: i32); +impl std::fmt::Debug for SQLite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "SQLite WASM bridge") + } } diff --git a/diesel-wasm-sqlite/src/lib.rs b/diesel-wasm-sqlite/src/lib.rs index d8add46ae..d97cbb813 100644 --- a/diesel-wasm-sqlite/src/lib.rs +++ b/diesel-wasm-sqlite/src/lib.rs @@ -6,88 +6,41 @@ pub mod query_builder; pub mod sqlite_types; pub mod utils; +#[cfg(not(target_arch = "wasm32"))] +compile_error!("This crate only suports the `wasm32-unknown-unknown` target"); + +use self::ffi::SQLite; use diesel::{ - connection::{AnsiTransactionManager, Instrumentation, SimpleConnection, TransactionManager}, - query_builder::{QueryFragment, QueryId}, + query_builder::{AsQuery, QueryFragment, QueryId}, result::QueryResult, - Connection, }; +use tokio::sync::OnceCell; use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; pub use backend::{SqliteType, WasmSqlite}; -#[derive(Debug)] -pub struct WasmSqliteConnection { - raw_db_pointer: JsValue, -} - -#[derive(Debug)] -pub struct WasmSqliteError(JsValue); - -impl SimpleConnection for WasmSqliteConnection { - fn batch_execute(&mut self, query: &str) -> diesel::prelude::QueryResult<()> { - ffi::batch_execute(&self.raw_db_pointer, query) - .map_err(WasmSqliteError::from) - .map_err(Into::into) - } -} +/// The SQLite Library +/// this global constant references the loaded SQLite WASM. +static SQLITE: OnceCell = OnceCell::const_new(); -impl diesel::connection::ConnectionSealed for WasmSqliteConnection {} +pub type SQLiteWasm = &'static JsValue; -pub async fn rust_establish( - database_url: &str, -) -> diesel::prelude::ConnectionResult { - let raw_conn = ffi::establish(database_url) +pub(crate) async fn get_sqlite() -> &'static SQLite { + SQLITE + .get_or_init(|| async { + let module = SQLite::wasm_module().await; + SQLite::new(module) + }) .await - .map_err(WasmSqliteError::from) - .map_err(Into::::into)?; - web_sys::console::log_1(&format!("raw conn: {:?}", raw_conn).into()); - Ok(WasmSqliteConnection { - raw_db_pointer: raw_conn, - }) } -unsafe impl Send for WasmSqliteConnection {} -impl Connection for WasmSqliteConnection { - type Backend = WasmSqlite; - type TransactionManager = AnsiTransactionManager; - fn establish(database_url: &str) -> diesel::prelude::ConnectionResult { - todo!(); - } - /* - fn establish(database_url: &str) -> diesel::prelude::ConnectionResult { - let raw_conn = ffi::establish(database_url) - .map_err(WasmSqliteError::from) - .map_err(Into::::into)?; - web_sys::console::log_1(&format!("raw conn: {:?}", raw_conn).into()); - Ok(WasmSqliteConnection { - raw_db_pointer: raw_conn, - }) - } - */ - - fn execute_returning_count(&mut self, source: &T) -> QueryResult - where - T: QueryFragment + QueryId, - { - todo!() - } - - fn transaction_state( - &mut self, - ) -> &mut >::TransactionStateData { - todo!() - } - - fn instrumentation(&mut self) -> &mut dyn Instrumentation { - todo!() - } - - fn set_instrumentation(&mut self, instrumentation: impl diesel::connection::Instrumentation) { - todo!() - } +pub(crate) fn get_sqlite_unchecked() -> &'static SQLite { + SQLITE.get().expect("SQLite is not initialized") } +#[derive(Debug)] +pub struct WasmSqliteError(JsValue); + impl From for diesel::result::Error { fn from(value: WasmSqliteError) -> diesel::result::Error { log::error!("NOT IMPLEMENTED, {:?}", value); @@ -108,25 +61,3 @@ impl From for WasmSqliteError { WasmSqliteError(err) } } - -/* -mod tests { - use super::*; - use wasm_bindgen_test::wasm_bindgen_test; - use web_sys::console; - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - /* - #[wasm_bindgen_test] - fn test_establish() { - let rng: u16 = rand::random(); - let url = format!( - "{}/wasmtest-{}.db3", - std::env::temp_dir().to_str().unwrap(), - rng - ); - let mut conn = WasmSqliteConnection::establish(&url).unwrap(); - println!("{:?}", conn); - } - */ -} -*/ diff --git a/diesel-wasm-sqlite/src/package.js b/diesel-wasm-sqlite/src/package.js index 649b64a76..ba3464210 100644 --- a/diesel-wasm-sqlite/src/package.js +++ b/diesel-wasm-sqlite/src/package.js @@ -1,13 +1,171 @@ // Primary result codes. // https://www.sqlite.org/rescode.html const SQLITE_OK = 0; +const SQLITE_ERROR = 1; +const SQLITE_INTERNAL = 2; +const SQLITE_PERM = 3; +const SQLITE_ABORT = 4; +const SQLITE_BUSY = 5; +const SQLITE_LOCKED = 6; +const SQLITE_NOMEM = 7; +const SQLITE_READONLY = 8; +const SQLITE_INTERRUPT = 9; +const SQLITE_IOERR = 10; +const SQLITE_CORRUPT = 11; +const SQLITE_NOTFOUND = 12; +const SQLITE_FULL = 13; +const SQLITE_CANTOPEN = 14; +const SQLITE_PROTOCOL = 15; +const SQLITE_EMPTY = 16; +const SQLITE_SCHEMA = 17; +const SQLITE_TOOBIG = 18; +const SQLITE_CONSTRAINT = 19; +const SQLITE_MISMATCH = 20; const SQLITE_MISUSE = 21; +const SQLITE_NOLFS = 22; +const SQLITE_AUTH = 23; +const SQLITE_FORMAT = 24; const SQLITE_RANGE = 25; +const SQLITE_NOTADB = 26; const SQLITE_NOTICE = 27; +const SQLITE_WARNING = 28; const SQLITE_ROW = 100; const SQLITE_DONE = 101; + +// Extended error codes. +const SQLITE_IOERR_ACCESS = 3338; +const SQLITE_IOERR_CHECKRESERVEDLOCK = 3594; +const SQLITE_IOERR_CLOSE = 4106; +const SQLITE_IOERR_DATA = 8202; +const SQLITE_IOERR_DELETE = 2570; +const SQLITE_IOERR_DELETE_NOENT = 5898; +const SQLITE_IOERR_DIR_FSYNC = 1290; +const SQLITE_IOERR_FSTAT = 1802; +const SQLITE_IOERR_FSYNC = 1034; +const SQLITE_IOERR_GETTEMPPATH = 6410; +const SQLITE_IOERR_LOCK = 3850; +const SQLITE_IOERR_NOMEM = 3082; +const SQLITE_IOERR_READ = 266; +const SQLITE_IOERR_RDLOCK = 2314; +const SQLITE_IOERR_SEEK = 5642; +const SQLITE_IOERR_SHORT_READ = 522; +const SQLITE_IOERR_TRUNCATE = 1546; +const SQLITE_IOERR_UNLOCK = 2058; +const SQLITE_IOERR_VNODE = 6922; +const SQLITE_IOERR_WRITE = 778; +const SQLITE_IOERR_BEGIN_ATOMIC = 7434; +const SQLITE_IOERR_COMMIT_ATOMIC = 7690; +const SQLITE_IOERR_ROLLBACK_ATOMIC = 7946; + +// Other extended result codes. +const SQLITE_CONSTRAINT_CHECK = 275; +const SQLITE_CONSTRAINT_COMMITHOOK = 531; +const SQLITE_CONSTRAINT_FOREIGNKEY = 787; +const SQLITE_CONSTRAINT_FUNCTION = 1043; +const SQLITE_CONSTRAINT_NOTNULL = 1299; +const SQLITE_CONSTRAINT_PINNED = 2835; +const SQLITE_CONSTRAINT_PRIMARYKEY = 1555; +const SQLITE_CONSTRAINT_ROWID = 2579; +const SQLITE_CONSTRAINT_TRIGGER = 1811; +const SQLITE_CONSTRAINT_UNIQUE = 2067; +const SQLITE_CONSTRAINT_VTAB = 2323; + +// Open flags. +// https://www.sqlite.org/c3ref/c_open_autoproxy.html +const SQLITE_OPEN_READONLY = 0x00000001; const SQLITE_OPEN_READWRITE = 0x00000002; const SQLITE_OPEN_CREATE = 0x00000004; +const SQLITE_OPEN_DELETEONCLOSE = 0x00000008; +const SQLITE_OPEN_EXCLUSIVE = 0x00000010; +const SQLITE_OPEN_AUTOPROXY = 0x00000020; +const SQLITE_OPEN_URI = 0x00000040; +const SQLITE_OPEN_MEMORY = 0x00000080; +const SQLITE_OPEN_MAIN_DB = 0x00000100; +const SQLITE_OPEN_TEMP_DB = 0x00000200; +const SQLITE_OPEN_TRANSIENT_DB = 0x00000400; +const SQLITE_OPEN_MAIN_JOURNAL = 0x00000800; +const SQLITE_OPEN_TEMP_JOURNAL = 0x00001000; +const SQLITE_OPEN_SUBJOURNAL = 0x00002000; +const SQLITE_OPEN_SUPER_JOURNAL = 0x00004000; +const SQLITE_OPEN_NOMUTEX = 0x00008000; +const SQLITE_OPEN_FULLMUTEX = 0x00010000; +const SQLITE_OPEN_SHAREDCACHE = 0x00020000; +const SQLITE_OPEN_PRIVATECACHE = 0x00040000; +const SQLITE_OPEN_WAL = 0x00080000; +const SQLITE_OPEN_NOFOLLOW = 0x01000000; + +// Locking levels. +// https://www.sqlite.org/c3ref/c_lock_exclusive.html +const SQLITE_LOCK_NONE = 0; +const SQLITE_LOCK_SHARED = 1; +const SQLITE_LOCK_RESERVED = 2; +const SQLITE_LOCK_PENDING = 3; +const SQLITE_LOCK_EXCLUSIVE = 4; + +// Device characteristics. +// https://www.sqlite.org/c3ref/c_iocap_atomic.html +const SQLITE_IOCAP_ATOMIC = 0x00000001; +const SQLITE_IOCAP_ATOMIC512 = 0x00000002; +const SQLITE_IOCAP_ATOMIC1K = 0x00000004; +const SQLITE_IOCAP_ATOMIC2K = 0x00000008; +const SQLITE_IOCAP_ATOMIC4K = 0x00000010; +const SQLITE_IOCAP_ATOMIC8K = 0x00000020; +const SQLITE_IOCAP_ATOMIC16K = 0x00000040; +const SQLITE_IOCAP_ATOMIC32K = 0x00000080; +const SQLITE_IOCAP_ATOMIC64K = 0x00000100; +const SQLITE_IOCAP_SAFE_APPEND = 0x00000200; +const SQLITE_IOCAP_SEQUENTIAL = 0x00000400; +const SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 0x00000800; +const SQLITE_IOCAP_POWERSAFE_OVERWRITE = 0x00001000; +const SQLITE_IOCAP_IMMUTABLE = 0x00002000; +const SQLITE_IOCAP_BATCH_ATOMIC = 0x00004000; + +// xAccess flags. +// https://www.sqlite.org/c3ref/c_access_exists.html +const SQLITE_ACCESS_EXISTS = 0; +const SQLITE_ACCESS_READWRITE = 1; +const SQLITE_ACCESS_READ = 2; + +// File control opcodes +// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite +const SQLITE_FCNTL_LOCKSTATE = 1; +const SQLITE_FCNTL_GET_LOCKPROXYFILE = 2; +const SQLITE_FCNTL_SET_LOCKPROXYFILE = 3; +const SQLITE_FCNTL_LAST_ERRNO = 4; +const SQLITE_FCNTL_SIZE_HINT = 5; +const SQLITE_FCNTL_CHUNK_SIZE = 6; +const SQLITE_FCNTL_FILE_POINTER = 7; +const SQLITE_FCNTL_SYNC_OMITTED = 8; +const SQLITE_FCNTL_WIN32_AV_RETRY = 9; +const SQLITE_FCNTL_PERSIST_WAL = 10; +const SQLITE_FCNTL_OVERWRITE = 11; +const SQLITE_FCNTL_VFSNAME = 12; +const SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13; +const SQLITE_FCNTL_PRAGMA = 14; +const SQLITE_FCNTL_BUSYHANDLER = 15; +const SQLITE_FCNTL_TEMPFILENAME = 16; +const SQLITE_FCNTL_MMAP_SIZE = 18; +const SQLITE_FCNTL_TRACE = 19; +const SQLITE_FCNTL_HAS_MOVED = 20; +const SQLITE_FCNTL_SYNC = 21; +const SQLITE_FCNTL_COMMIT_PHASETWO = 22; +const SQLITE_FCNTL_WIN32_SET_HANDLE = 23; +const SQLITE_FCNTL_WAL_BLOCK = 24; +const SQLITE_FCNTL_ZIPVFS = 25; +const SQLITE_FCNTL_RBU = 26; +const SQLITE_FCNTL_VFS_POINTER = 27; +const SQLITE_FCNTL_JOURNAL_POINTER = 28; +const SQLITE_FCNTL_WIN32_GET_HANDLE = 29; +const SQLITE_FCNTL_PDB = 30; +const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31; +const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32; +const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33; +const SQLITE_FCNTL_LOCK_TIMEOUT = 34; +const SQLITE_FCNTL_DATA_VERSION = 35; +const SQLITE_FCNTL_SIZE_LIMIT = 36; +const SQLITE_FCNTL_CKPT_DONE = 37; +const SQLITE_FCNTL_RESERVE_BYTES = 38; +const SQLITE_FCNTL_CKPT_START = 39; // Fundamental datatypes. // https://www.sqlite.org/c3ref/c_blob.html @@ -17,6 +175,105 @@ const SQLITE_TEXT = 3; const SQLITE_BLOB = 4; const SQLITE_NULL = 5; +// Special destructor behavior. +// https://www.sqlite.org/c3ref/c_static.html +const SQLITE_STATIC = 0; +const SQLITE_TRANSIENT = -1; + +// Text encodings. +// https://sqlite.org/c3ref/c_any.html +const SQLITE_UTF8 = 1; /* IMP: R-37514-35566 */ +const SQLITE_UTF16LE = 2; /* IMP: R-03371-37637 */ +const SQLITE_UTF16BE = 3; /* IMP: R-51971-34154 */ +const SQLITE_UTF16 = 4; /* Use native byte order */ + +// Module constraint ops. +const SQLITE_INDEX_CONSTRAINT_EQ = 2; +const SQLITE_INDEX_CONSTRAINT_GT = 4; +const SQLITE_INDEX_CONSTRAINT_LE = 8; +const SQLITE_INDEX_CONSTRAINT_LT = 16; +const SQLITE_INDEX_CONSTRAINT_GE = 32; +const SQLITE_INDEX_CONSTRAINT_MATCH = 64; +const SQLITE_INDEX_CONSTRAINT_LIKE = 65; +const SQLITE_INDEX_CONSTRAINT_GLOB = 66; +const SQLITE_INDEX_CONSTRAINT_REGEXP = 67; +const SQLITE_INDEX_CONSTRAINT_NE = 68; +const SQLITE_INDEX_CONSTRAINT_ISNOT = 69; +const SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70; +const SQLITE_INDEX_CONSTRAINT_ISNULL = 71; +const SQLITE_INDEX_CONSTRAINT_IS = 72; +const SQLITE_INDEX_CONSTRAINT_FUNCTION = 150; +const SQLITE_INDEX_SCAN_UNIQUE = 1; /* Scan visits at most = 1 row */ + +// Function flags +const SQLITE_DETERMINISTIC = 0x000000800; +const SQLITE_DIRECTONLY = 0x000080000; +const SQLITE_SUBTYPE = 0x000100000; +const SQLITE_INNOCUOUS = 0x000200000; + +// Sync flags +const SQLITE_SYNC_NORMAL = 0x00002; +const SQLITE_SYNC_FULL = 0x00003; +const SQLITE_SYNC_DATAONLY = 0x00010; + +// Authorizer action codes +const SQLITE_CREATE_INDEX = 1; +const SQLITE_CREATE_TABLE = 2; +const SQLITE_CREATE_TEMP_INDEX = 3; +const SQLITE_CREATE_TEMP_TABLE = 4; +const SQLITE_CREATE_TEMP_TRIGGER = 5; +const SQLITE_CREATE_TEMP_VIEW = 6; +const SQLITE_CREATE_TRIGGER = 7; +const SQLITE_CREATE_VIEW = 8; +const SQLITE_DELETE = 9; +const SQLITE_DROP_INDEX = 10; +const SQLITE_DROP_TABLE = 11; +const SQLITE_DROP_TEMP_INDEX = 12; +const SQLITE_DROP_TEMP_TABLE = 13; +const SQLITE_DROP_TEMP_TRIGGER = 14; +const SQLITE_DROP_TEMP_VIEW = 15; +const SQLITE_DROP_TRIGGER = 16; +const SQLITE_DROP_VIEW = 17; +const SQLITE_INSERT = 18; +const SQLITE_PRAGMA = 19; +const SQLITE_READ = 20; +const SQLITE_SELECT = 21; +const SQLITE_TRANSACTION = 22; +const SQLITE_UPDATE = 23; +const SQLITE_ATTACH = 24; +const SQLITE_DETACH = 25; +const SQLITE_ALTER_TABLE = 26; +const SQLITE_REINDEX = 27; +const SQLITE_ANALYZE = 28; +const SQLITE_CREATE_VTABLE = 29; +const SQLITE_DROP_VTABLE = 30; +const SQLITE_FUNCTION = 31; +const SQLITE_SAVEPOINT = 32; +const SQLITE_COPY = 0; +const SQLITE_RECURSIVE = 33; + +// Authorizer return codes +const SQLITE_DENY = 1; +const SQLITE_IGNORE = 2; + +// Limit categories +const SQLITE_LIMIT_LENGTH = 0; +const SQLITE_LIMIT_SQL_LENGTH = 1; +const SQLITE_LIMIT_COLUMN = 2; +const SQLITE_LIMIT_EXPR_DEPTH = 3; +const SQLITE_LIMIT_COMPOUND_SELECT = 4; +const SQLITE_LIMIT_VDBE_OP = 5; +const SQLITE_LIMIT_FUNCTION_ARG = 6; +const SQLITE_LIMIT_ATTACHED = 7; +const SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8; +const SQLITE_LIMIT_VARIABLE_NUMBER = 9; +const SQLITE_LIMIT_TRIGGER_DEPTH = 10; +const SQLITE_LIMIT_WORKER_THREADS = 11; + +const SQLITE_PREPARE_PERSISTENT = 0x01; +const SQLITE_PREPARE_NORMALIZED = 0x02; +const SQLITE_PREPARE_NO_VTAB = 0x04; + // Copyright 2021 Roy T. Hashimoto. All Rights Reserved. @@ -883,6 +1140,244 @@ function decl(s) { return result; } +var WasmSQLiteLibrary = /*#__PURE__*/Object.freeze({ + __proto__: null, + Factory: Factory, + SQLITE_ABORT: SQLITE_ABORT, + SQLITE_ACCESS_EXISTS: SQLITE_ACCESS_EXISTS, + SQLITE_ACCESS_READ: SQLITE_ACCESS_READ, + SQLITE_ACCESS_READWRITE: SQLITE_ACCESS_READWRITE, + SQLITE_ALTER_TABLE: SQLITE_ALTER_TABLE, + SQLITE_ANALYZE: SQLITE_ANALYZE, + SQLITE_ATTACH: SQLITE_ATTACH, + SQLITE_AUTH: SQLITE_AUTH, + SQLITE_BLOB: SQLITE_BLOB, + SQLITE_BUSY: SQLITE_BUSY, + SQLITE_CANTOPEN: SQLITE_CANTOPEN, + SQLITE_CONSTRAINT: SQLITE_CONSTRAINT, + SQLITE_CONSTRAINT_CHECK: SQLITE_CONSTRAINT_CHECK, + SQLITE_CONSTRAINT_COMMITHOOK: SQLITE_CONSTRAINT_COMMITHOOK, + SQLITE_CONSTRAINT_FOREIGNKEY: SQLITE_CONSTRAINT_FOREIGNKEY, + SQLITE_CONSTRAINT_FUNCTION: SQLITE_CONSTRAINT_FUNCTION, + SQLITE_CONSTRAINT_NOTNULL: SQLITE_CONSTRAINT_NOTNULL, + SQLITE_CONSTRAINT_PINNED: SQLITE_CONSTRAINT_PINNED, + SQLITE_CONSTRAINT_PRIMARYKEY: SQLITE_CONSTRAINT_PRIMARYKEY, + SQLITE_CONSTRAINT_ROWID: SQLITE_CONSTRAINT_ROWID, + SQLITE_CONSTRAINT_TRIGGER: SQLITE_CONSTRAINT_TRIGGER, + SQLITE_CONSTRAINT_UNIQUE: SQLITE_CONSTRAINT_UNIQUE, + SQLITE_CONSTRAINT_VTAB: SQLITE_CONSTRAINT_VTAB, + SQLITE_COPY: SQLITE_COPY, + SQLITE_CORRUPT: SQLITE_CORRUPT, + SQLITE_CREATE_INDEX: SQLITE_CREATE_INDEX, + SQLITE_CREATE_TABLE: SQLITE_CREATE_TABLE, + SQLITE_CREATE_TEMP_INDEX: SQLITE_CREATE_TEMP_INDEX, + SQLITE_CREATE_TEMP_TABLE: SQLITE_CREATE_TEMP_TABLE, + SQLITE_CREATE_TEMP_TRIGGER: SQLITE_CREATE_TEMP_TRIGGER, + SQLITE_CREATE_TEMP_VIEW: SQLITE_CREATE_TEMP_VIEW, + SQLITE_CREATE_TRIGGER: SQLITE_CREATE_TRIGGER, + SQLITE_CREATE_VIEW: SQLITE_CREATE_VIEW, + SQLITE_CREATE_VTABLE: SQLITE_CREATE_VTABLE, + SQLITE_DELETE: SQLITE_DELETE, + SQLITE_DENY: SQLITE_DENY, + SQLITE_DETACH: SQLITE_DETACH, + SQLITE_DETERMINISTIC: SQLITE_DETERMINISTIC, + SQLITE_DIRECTONLY: SQLITE_DIRECTONLY, + SQLITE_DONE: SQLITE_DONE, + SQLITE_DROP_INDEX: SQLITE_DROP_INDEX, + SQLITE_DROP_TABLE: SQLITE_DROP_TABLE, + SQLITE_DROP_TEMP_INDEX: SQLITE_DROP_TEMP_INDEX, + SQLITE_DROP_TEMP_TABLE: SQLITE_DROP_TEMP_TABLE, + SQLITE_DROP_TEMP_TRIGGER: SQLITE_DROP_TEMP_TRIGGER, + SQLITE_DROP_TEMP_VIEW: SQLITE_DROP_TEMP_VIEW, + SQLITE_DROP_TRIGGER: SQLITE_DROP_TRIGGER, + SQLITE_DROP_VIEW: SQLITE_DROP_VIEW, + SQLITE_DROP_VTABLE: SQLITE_DROP_VTABLE, + SQLITE_EMPTY: SQLITE_EMPTY, + SQLITE_ERROR: SQLITE_ERROR, + SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, + SQLITE_FCNTL_BUSYHANDLER: SQLITE_FCNTL_BUSYHANDLER, + SQLITE_FCNTL_CHUNK_SIZE: SQLITE_FCNTL_CHUNK_SIZE, + SQLITE_FCNTL_CKPT_DONE: SQLITE_FCNTL_CKPT_DONE, + SQLITE_FCNTL_CKPT_START: SQLITE_FCNTL_CKPT_START, + SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, + SQLITE_FCNTL_COMMIT_PHASETWO: SQLITE_FCNTL_COMMIT_PHASETWO, + SQLITE_FCNTL_DATA_VERSION: SQLITE_FCNTL_DATA_VERSION, + SQLITE_FCNTL_FILE_POINTER: SQLITE_FCNTL_FILE_POINTER, + SQLITE_FCNTL_GET_LOCKPROXYFILE: SQLITE_FCNTL_GET_LOCKPROXYFILE, + SQLITE_FCNTL_HAS_MOVED: SQLITE_FCNTL_HAS_MOVED, + SQLITE_FCNTL_JOURNAL_POINTER: SQLITE_FCNTL_JOURNAL_POINTER, + SQLITE_FCNTL_LAST_ERRNO: SQLITE_FCNTL_LAST_ERRNO, + SQLITE_FCNTL_LOCKSTATE: SQLITE_FCNTL_LOCKSTATE, + SQLITE_FCNTL_LOCK_TIMEOUT: SQLITE_FCNTL_LOCK_TIMEOUT, + SQLITE_FCNTL_MMAP_SIZE: SQLITE_FCNTL_MMAP_SIZE, + SQLITE_FCNTL_OVERWRITE: SQLITE_FCNTL_OVERWRITE, + SQLITE_FCNTL_PDB: SQLITE_FCNTL_PDB, + SQLITE_FCNTL_PERSIST_WAL: SQLITE_FCNTL_PERSIST_WAL, + SQLITE_FCNTL_POWERSAFE_OVERWRITE: SQLITE_FCNTL_POWERSAFE_OVERWRITE, + SQLITE_FCNTL_PRAGMA: SQLITE_FCNTL_PRAGMA, + SQLITE_FCNTL_RBU: SQLITE_FCNTL_RBU, + SQLITE_FCNTL_RESERVE_BYTES: SQLITE_FCNTL_RESERVE_BYTES, + SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE, + SQLITE_FCNTL_SET_LOCKPROXYFILE: SQLITE_FCNTL_SET_LOCKPROXYFILE, + SQLITE_FCNTL_SIZE_HINT: SQLITE_FCNTL_SIZE_HINT, + SQLITE_FCNTL_SIZE_LIMIT: SQLITE_FCNTL_SIZE_LIMIT, + SQLITE_FCNTL_SYNC: SQLITE_FCNTL_SYNC, + SQLITE_FCNTL_SYNC_OMITTED: SQLITE_FCNTL_SYNC_OMITTED, + SQLITE_FCNTL_TEMPFILENAME: SQLITE_FCNTL_TEMPFILENAME, + SQLITE_FCNTL_TRACE: SQLITE_FCNTL_TRACE, + SQLITE_FCNTL_VFSNAME: SQLITE_FCNTL_VFSNAME, + SQLITE_FCNTL_VFS_POINTER: SQLITE_FCNTL_VFS_POINTER, + SQLITE_FCNTL_WAL_BLOCK: SQLITE_FCNTL_WAL_BLOCK, + SQLITE_FCNTL_WIN32_AV_RETRY: SQLITE_FCNTL_WIN32_AV_RETRY, + SQLITE_FCNTL_WIN32_GET_HANDLE: SQLITE_FCNTL_WIN32_GET_HANDLE, + SQLITE_FCNTL_WIN32_SET_HANDLE: SQLITE_FCNTL_WIN32_SET_HANDLE, + SQLITE_FCNTL_ZIPVFS: SQLITE_FCNTL_ZIPVFS, + SQLITE_FLOAT: SQLITE_FLOAT, + SQLITE_FORMAT: SQLITE_FORMAT, + SQLITE_FULL: SQLITE_FULL, + SQLITE_FUNCTION: SQLITE_FUNCTION, + SQLITE_IGNORE: SQLITE_IGNORE, + SQLITE_INDEX_CONSTRAINT_EQ: SQLITE_INDEX_CONSTRAINT_EQ, + SQLITE_INDEX_CONSTRAINT_FUNCTION: SQLITE_INDEX_CONSTRAINT_FUNCTION, + SQLITE_INDEX_CONSTRAINT_GE: SQLITE_INDEX_CONSTRAINT_GE, + SQLITE_INDEX_CONSTRAINT_GLOB: SQLITE_INDEX_CONSTRAINT_GLOB, + SQLITE_INDEX_CONSTRAINT_GT: SQLITE_INDEX_CONSTRAINT_GT, + SQLITE_INDEX_CONSTRAINT_IS: SQLITE_INDEX_CONSTRAINT_IS, + SQLITE_INDEX_CONSTRAINT_ISNOT: SQLITE_INDEX_CONSTRAINT_ISNOT, + SQLITE_INDEX_CONSTRAINT_ISNOTNULL: SQLITE_INDEX_CONSTRAINT_ISNOTNULL, + SQLITE_INDEX_CONSTRAINT_ISNULL: SQLITE_INDEX_CONSTRAINT_ISNULL, + SQLITE_INDEX_CONSTRAINT_LE: SQLITE_INDEX_CONSTRAINT_LE, + SQLITE_INDEX_CONSTRAINT_LIKE: SQLITE_INDEX_CONSTRAINT_LIKE, + SQLITE_INDEX_CONSTRAINT_LT: SQLITE_INDEX_CONSTRAINT_LT, + SQLITE_INDEX_CONSTRAINT_MATCH: SQLITE_INDEX_CONSTRAINT_MATCH, + SQLITE_INDEX_CONSTRAINT_NE: SQLITE_INDEX_CONSTRAINT_NE, + SQLITE_INDEX_CONSTRAINT_REGEXP: SQLITE_INDEX_CONSTRAINT_REGEXP, + SQLITE_INDEX_SCAN_UNIQUE: SQLITE_INDEX_SCAN_UNIQUE, + SQLITE_INNOCUOUS: SQLITE_INNOCUOUS, + SQLITE_INSERT: SQLITE_INSERT, + SQLITE_INTEGER: SQLITE_INTEGER, + SQLITE_INTERNAL: SQLITE_INTERNAL, + SQLITE_INTERRUPT: SQLITE_INTERRUPT, + SQLITE_IOCAP_ATOMIC: SQLITE_IOCAP_ATOMIC, + SQLITE_IOCAP_ATOMIC16K: SQLITE_IOCAP_ATOMIC16K, + SQLITE_IOCAP_ATOMIC1K: SQLITE_IOCAP_ATOMIC1K, + SQLITE_IOCAP_ATOMIC2K: SQLITE_IOCAP_ATOMIC2K, + SQLITE_IOCAP_ATOMIC32K: SQLITE_IOCAP_ATOMIC32K, + SQLITE_IOCAP_ATOMIC4K: SQLITE_IOCAP_ATOMIC4K, + SQLITE_IOCAP_ATOMIC512: SQLITE_IOCAP_ATOMIC512, + SQLITE_IOCAP_ATOMIC64K: SQLITE_IOCAP_ATOMIC64K, + SQLITE_IOCAP_ATOMIC8K: SQLITE_IOCAP_ATOMIC8K, + SQLITE_IOCAP_BATCH_ATOMIC: SQLITE_IOCAP_BATCH_ATOMIC, + SQLITE_IOCAP_IMMUTABLE: SQLITE_IOCAP_IMMUTABLE, + SQLITE_IOCAP_POWERSAFE_OVERWRITE: SQLITE_IOCAP_POWERSAFE_OVERWRITE, + SQLITE_IOCAP_SAFE_APPEND: SQLITE_IOCAP_SAFE_APPEND, + SQLITE_IOCAP_SEQUENTIAL: SQLITE_IOCAP_SEQUENTIAL, + SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN: SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN, + SQLITE_IOERR: SQLITE_IOERR, + SQLITE_IOERR_ACCESS: SQLITE_IOERR_ACCESS, + SQLITE_IOERR_BEGIN_ATOMIC: SQLITE_IOERR_BEGIN_ATOMIC, + SQLITE_IOERR_CHECKRESERVEDLOCK: SQLITE_IOERR_CHECKRESERVEDLOCK, + SQLITE_IOERR_CLOSE: SQLITE_IOERR_CLOSE, + SQLITE_IOERR_COMMIT_ATOMIC: SQLITE_IOERR_COMMIT_ATOMIC, + SQLITE_IOERR_DATA: SQLITE_IOERR_DATA, + SQLITE_IOERR_DELETE: SQLITE_IOERR_DELETE, + SQLITE_IOERR_DELETE_NOENT: SQLITE_IOERR_DELETE_NOENT, + SQLITE_IOERR_DIR_FSYNC: SQLITE_IOERR_DIR_FSYNC, + SQLITE_IOERR_FSTAT: SQLITE_IOERR_FSTAT, + SQLITE_IOERR_FSYNC: SQLITE_IOERR_FSYNC, + SQLITE_IOERR_GETTEMPPATH: SQLITE_IOERR_GETTEMPPATH, + SQLITE_IOERR_LOCK: SQLITE_IOERR_LOCK, + SQLITE_IOERR_NOMEM: SQLITE_IOERR_NOMEM, + SQLITE_IOERR_RDLOCK: SQLITE_IOERR_RDLOCK, + SQLITE_IOERR_READ: SQLITE_IOERR_READ, + SQLITE_IOERR_ROLLBACK_ATOMIC: SQLITE_IOERR_ROLLBACK_ATOMIC, + SQLITE_IOERR_SEEK: SQLITE_IOERR_SEEK, + SQLITE_IOERR_SHORT_READ: SQLITE_IOERR_SHORT_READ, + SQLITE_IOERR_TRUNCATE: SQLITE_IOERR_TRUNCATE, + SQLITE_IOERR_UNLOCK: SQLITE_IOERR_UNLOCK, + SQLITE_IOERR_VNODE: SQLITE_IOERR_VNODE, + SQLITE_IOERR_WRITE: SQLITE_IOERR_WRITE, + SQLITE_LIMIT_ATTACHED: SQLITE_LIMIT_ATTACHED, + SQLITE_LIMIT_COLUMN: SQLITE_LIMIT_COLUMN, + SQLITE_LIMIT_COMPOUND_SELECT: SQLITE_LIMIT_COMPOUND_SELECT, + SQLITE_LIMIT_EXPR_DEPTH: SQLITE_LIMIT_EXPR_DEPTH, + SQLITE_LIMIT_FUNCTION_ARG: SQLITE_LIMIT_FUNCTION_ARG, + SQLITE_LIMIT_LENGTH: SQLITE_LIMIT_LENGTH, + SQLITE_LIMIT_LIKE_PATTERN_LENGTH: SQLITE_LIMIT_LIKE_PATTERN_LENGTH, + SQLITE_LIMIT_SQL_LENGTH: SQLITE_LIMIT_SQL_LENGTH, + SQLITE_LIMIT_TRIGGER_DEPTH: SQLITE_LIMIT_TRIGGER_DEPTH, + SQLITE_LIMIT_VARIABLE_NUMBER: SQLITE_LIMIT_VARIABLE_NUMBER, + SQLITE_LIMIT_VDBE_OP: SQLITE_LIMIT_VDBE_OP, + SQLITE_LIMIT_WORKER_THREADS: SQLITE_LIMIT_WORKER_THREADS, + SQLITE_LOCKED: SQLITE_LOCKED, + SQLITE_LOCK_EXCLUSIVE: SQLITE_LOCK_EXCLUSIVE, + SQLITE_LOCK_NONE: SQLITE_LOCK_NONE, + SQLITE_LOCK_PENDING: SQLITE_LOCK_PENDING, + SQLITE_LOCK_RESERVED: SQLITE_LOCK_RESERVED, + SQLITE_LOCK_SHARED: SQLITE_LOCK_SHARED, + SQLITE_MISMATCH: SQLITE_MISMATCH, + SQLITE_MISUSE: SQLITE_MISUSE, + SQLITE_NOLFS: SQLITE_NOLFS, + SQLITE_NOMEM: SQLITE_NOMEM, + SQLITE_NOTADB: SQLITE_NOTADB, + SQLITE_NOTFOUND: SQLITE_NOTFOUND, + SQLITE_NOTICE: SQLITE_NOTICE, + SQLITE_NULL: SQLITE_NULL, + SQLITE_OK: SQLITE_OK, + SQLITE_OPEN_AUTOPROXY: SQLITE_OPEN_AUTOPROXY, + SQLITE_OPEN_CREATE: SQLITE_OPEN_CREATE, + SQLITE_OPEN_DELETEONCLOSE: SQLITE_OPEN_DELETEONCLOSE, + SQLITE_OPEN_EXCLUSIVE: SQLITE_OPEN_EXCLUSIVE, + SQLITE_OPEN_FULLMUTEX: SQLITE_OPEN_FULLMUTEX, + SQLITE_OPEN_MAIN_DB: SQLITE_OPEN_MAIN_DB, + SQLITE_OPEN_MAIN_JOURNAL: SQLITE_OPEN_MAIN_JOURNAL, + SQLITE_OPEN_MEMORY: SQLITE_OPEN_MEMORY, + SQLITE_OPEN_NOFOLLOW: SQLITE_OPEN_NOFOLLOW, + SQLITE_OPEN_NOMUTEX: SQLITE_OPEN_NOMUTEX, + SQLITE_OPEN_PRIVATECACHE: SQLITE_OPEN_PRIVATECACHE, + SQLITE_OPEN_READONLY: SQLITE_OPEN_READONLY, + SQLITE_OPEN_READWRITE: SQLITE_OPEN_READWRITE, + SQLITE_OPEN_SHAREDCACHE: SQLITE_OPEN_SHAREDCACHE, + SQLITE_OPEN_SUBJOURNAL: SQLITE_OPEN_SUBJOURNAL, + SQLITE_OPEN_SUPER_JOURNAL: SQLITE_OPEN_SUPER_JOURNAL, + SQLITE_OPEN_TEMP_DB: SQLITE_OPEN_TEMP_DB, + SQLITE_OPEN_TEMP_JOURNAL: SQLITE_OPEN_TEMP_JOURNAL, + SQLITE_OPEN_TRANSIENT_DB: SQLITE_OPEN_TRANSIENT_DB, + SQLITE_OPEN_URI: SQLITE_OPEN_URI, + SQLITE_OPEN_WAL: SQLITE_OPEN_WAL, + SQLITE_PERM: SQLITE_PERM, + SQLITE_PRAGMA: SQLITE_PRAGMA, + SQLITE_PREPARE_NORMALIZED: SQLITE_PREPARE_NORMALIZED, + SQLITE_PREPARE_NO_VTAB: SQLITE_PREPARE_NO_VTAB, + SQLITE_PREPARE_PERSISTENT: SQLITE_PREPARE_PERSISTENT, + SQLITE_PROTOCOL: SQLITE_PROTOCOL, + SQLITE_RANGE: SQLITE_RANGE, + SQLITE_READ: SQLITE_READ, + SQLITE_READONLY: SQLITE_READONLY, + SQLITE_RECURSIVE: SQLITE_RECURSIVE, + SQLITE_REINDEX: SQLITE_REINDEX, + SQLITE_ROW: SQLITE_ROW, + SQLITE_SAVEPOINT: SQLITE_SAVEPOINT, + SQLITE_SCHEMA: SQLITE_SCHEMA, + SQLITE_SELECT: SQLITE_SELECT, + SQLITE_STATIC: SQLITE_STATIC, + SQLITE_SUBTYPE: SQLITE_SUBTYPE, + SQLITE_SYNC_DATAONLY: SQLITE_SYNC_DATAONLY, + SQLITE_SYNC_FULL: SQLITE_SYNC_FULL, + SQLITE_SYNC_NORMAL: SQLITE_SYNC_NORMAL, + SQLITE_TEXT: SQLITE_TEXT, + SQLITE_TOOBIG: SQLITE_TOOBIG, + SQLITE_TRANSACTION: SQLITE_TRANSACTION, + SQLITE_TRANSIENT: SQLITE_TRANSIENT, + SQLITE_UPDATE: SQLITE_UPDATE, + SQLITE_UTF16: SQLITE_UTF16, + SQLITE_UTF16BE: SQLITE_UTF16BE, + SQLITE_UTF16LE: SQLITE_UTF16LE, + SQLITE_UTF8: SQLITE_UTF8, + SQLITE_WARNING: SQLITE_WARNING, + SQLiteError: SQLiteError +}); + var Module = (() => { var _scriptName = import.meta.url; @@ -912,55 +1407,84 @@ function base64Decode(str) { return bytes.buffer; } -const module = await Module({ - "wasmBinary": base64Decode(base64Wasm), -}); +class SQLite { + #module; + #sqlite3; + constructor(module) { + if (typeof module === "undefined") { + throw new Error("Cannot be called directly"); + } -// const module = await initWasmModule(); -const sqlite3 = Factory(module); + this.sqlite3 = Factory(module); + } -function sqlite3_result_text(context, value) { - sqlite3.result_text(context, value); -} + static async wasm_module() { + return await Module({ + "wasmBinary": base64Decode(base64Wasm), + }); + } -function sqlite3_result_int(context, value) { - sqlite3.result_int(context, value); -} + static async build() { + const module = await Module({ + "wasmBinary": base64Decode(base64Wasm), + }); + return new WasmSQLiteLibrary(module); + } -function sqlite3_result_int64(context, value) { - sqlite3.result_int64(context, value); -} + result_text(context, value) { + this.sqlite3.result_text(context, value); + } -function sqlite3_result_double(context, value) { - sqlite3.result_double(context, value); -} + result_int(context, value) { + this.sqlite3.result_int(context, value); + } -function sqlite3_result_blob(context, value) { - sqlite3.result_blob(context, value); -} + result_int64(context, value) { + this.sqlite3.result_int64(context, value); + } -function sqlite3_result_null(context) { - sqlite3.result_null(context); -} + result_double(context, value) { + this.sqlite3.result_double(context, value); + } -async function establish(database_url) { - try { - console.log("Opening database!", database_url); - let db = await sqlite3.open_v2(database_url); - console.log(db); - return db; - } catch { - console.log("establish err"); + result_blob(context, value) { + this.sqlite3.result_blob(context, value); } -} -function batch_execute(database, query) { - try { - sqlite3.exec(database, query); - console.log("Batch exec'ed"); - } catch { - console.log("exec err"); + result_null(context) { + this.sqlite3.result_null(context); + } + + async open_v2(database_url, iflags) { + try { + console.log("Opening database!", database_url); + let db = await this.sqlite3.open_v2(database_url, iflags); + return db; + } catch { + console.log("openv2 error"); + } + } + + async exec(db, query) { + try { + return await this.sqlite3.exec(db, query); + } catch { + console.log('exec err'); + } + } + + changes(db) { + return this.sqlite3.changes(db); + } + + batch_execute(database, query) { + try { + sqlite3.exec(database, query); + console.log("Batch exec'ed"); + } catch { + console.log("exec err"); + } } } -export { batch_execute, establish, sqlite3_result_blob, sqlite3_result_double, sqlite3_result_int, sqlite3_result_int64, sqlite3_result_null, sqlite3_result_text }; +export { SQLite }; diff --git a/diesel-wasm-sqlite/src/query_builder/returning.rs b/diesel-wasm-sqlite/src/query_builder/returning.rs index edc466034..055cab509 100644 --- a/diesel-wasm-sqlite/src/query_builder/returning.rs +++ b/diesel-wasm-sqlite/src/query_builder/returning.rs @@ -2,7 +2,7 @@ use crate::backend::{SqliteReturningClause, WasmSqlite}; use diesel::query_builder::ReturningClause; use diesel::query_builder::{AstPass, QueryFragment}; use diesel::result::QueryResult; - +/* impl QueryFragment for ReturningClause where Expr: QueryFragment, @@ -13,4 +13,4 @@ where self.0.walk_ast(out.reborrow())?; Ok(()) } -} +}*/ diff --git a/diesel-wasm-sqlite/src/sqlite_types.rs b/diesel-wasm-sqlite/src/sqlite_types.rs index 75c2d0d04..a9f1838f7 100644 --- a/diesel-wasm-sqlite/src/sqlite_types.rs +++ b/diesel-wasm-sqlite/src/sqlite_types.rs @@ -1,4 +1,5 @@ use super::backend::{SqliteType, WasmSqlite}; +use bitflags::bitflags; use diesel::sql_types::*; macro_rules! impl_has_sql_type { @@ -23,3 +24,30 @@ impl_has_sql_type!(Binary, SqliteType::Binary); impl_has_sql_type!(Date, SqliteType::Text); impl_has_sql_type!(Time, SqliteType::Text); impl_has_sql_type!(Timestamp, SqliteType::Text); + +bitflags! { + pub struct SqliteOpenFlags: u32 { + const SQLITE_OPEN_READONLY = 0x00000001; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_READWRITE = 0x00000002; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_CREATE = 0x00000004; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_DELETEONCLOSE = 0x00000008; /* VFS only */ + const SQLITE_OPEN_EXCLUSIVE = 0x00000010; /* VFS only */ + const SQLITE_OPEN_AUTOPROXY = 0x00000020; /* VFS only */ + const SQLITE_OPEN_URI = 0x00000040; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_MEMORY = 0x00000080; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_MAIN_DB = 0x00000100; /* VFS only */ + const SQLITE_OPEN_TEMP_DB = 0x00000200; /* VFS only */ + const SQLITE_OPEN_TRANSIENT_DB = 0x00000400; /* VFS only */ + const SQLITE_OPEN_MAIN_JOURNAL = 0x00000800; /* VFS only */ + const SQLITE_OPEN_TEMP_JOURNAL = 0x00001000; /* VFS only */ + const SQLITE_OPEN_SUBJOURNAL = 0x00002000; /* VFS only */ + const SQLITE_OPEN_SUPER_JOURNAL = 0x00004000; /* VFS only */ + const SQLITE_OPEN_NOMUTEX = 0x00008000; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_FULLMUTEX = 0x00010000; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_SHAREDCACHE = 0x00020000; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_PRIVATECACHE = 0x00040000; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_WAL = 0x00080000; /* VFS only */ + const SQLITE_OPEN_NOFOLLOW = 0x01000000; /* Ok for sqlite3_open_v2() */ + const SQLITE_OPEN_EXRESCODE = 0x02000000; /* Extended result codes */ + } +} diff --git a/diesel-wasm-sqlite/tests/web.rs b/diesel-wasm-sqlite/tests/web.rs index 02e52bc53..dd8a6673a 100755 --- a/diesel-wasm-sqlite/tests/web.rs +++ b/diesel-wasm-sqlite/tests/web.rs @@ -4,7 +4,7 @@ use diesel::connection::Connection; use diesel_wasm_sqlite::rust_establish; use wasm_bindgen_test::*; use web_sys::console; -wasm_bindgen_test_configure!(run_in_browser); +wasm_bindgen_test_configure!(run_in_dedicated_worker); #[wasm_bindgen_test] async fn test_establish() {