diff --git a/Cargo.lock b/Cargo.lock index fbecdb9155..fcfd417f5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,13 +689,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.106" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066fce287b1d4eafef758e89e09d724a24808a9196fe9756b8ca90e86d0719a2" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -1474,9 +1474,7 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.324.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24503eda646f246aa6eb0f794909f9a857c8f05095fed66f36e0eaef92edce23" +version = "0.325.0" dependencies = [ "anyhow", "az", @@ -1948,6 +1946,7 @@ dependencies = [ "k256", "lazy-regex", "libc", + "libsqlite3-sys", "libz-sys", "md-5", "md4", @@ -1970,6 +1969,7 @@ dependencies = [ "ring", "ripemd", "rsa", + "rusqlite", "scrypt", "sec1", "serde", @@ -2043,9 +2043,7 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.200.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a529a2c488cd3042f12f35666569ebe5b3cf89d2b7d1cafc1a652f6d7bcc8f" +version = "0.201.0" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -4569,7 +4567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4590,9 +4588,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b694a822684ddb75df4d657029161431bcb4a85c1856952f845b76912bc6fec" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -6685,9 +6683,7 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.233.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307f176b7475480cee690c34c7118f96fe564d1f2a974bf990294b8310ae4983" +version = "0.234.0" dependencies = [ "num-bigint", "serde", @@ -8272,9 +8268,9 @@ dependencies = [ [[package]] name = "v8" -version = "130.0.1" +version = "130.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c23b5c2caff00209b03a716609b275acae94b02dd3b63c4648e7232a84a8402f" +checksum = "2ee0be58935708fa4d7efb970c6cf9f2d9511d24ee24246481a65b6ee167348d" dependencies = [ "bindgen", "bitflags 2.6.0", @@ -9145,7 +9141,3 @@ dependencies = [ "cc", "pkg-config", ] - -[[patch.unused]] -name = "deno_core" -version = "0.325.0" diff --git a/Cargo.toml b/Cargo.toml index 17b4a8ea7d..d46b0cd927 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ repository = "https://github.com/denoland/deno" [workspace.dependencies] deno_ast = { version = "=0.44.0", features = ["transpiling"] } -deno_core = { version = "0.324.0" } +deno_core = { version = "0.325.0" } deno_bench_util = { version = "0.175.0", path = "./bench_util" } deno_config = { version = "=0.39.3", features = ["workspace", "sync"] } diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index 9cd09f6fde..f010e5784f 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -60,6 +60,7 @@ ipnetwork = "0.20.0" k256 = "0.13.1" lazy-regex.workspace = true libc.workspace = true +libsqlite3-sys = "0.30.1" libz-sys.workspace = true md-5 = { version = "0.10.5", features = ["oid"] } md4 = "0.10.2" @@ -82,6 +83,7 @@ regex.workspace = true ring.workspace = true ripemd = { version = "0.1.3", features = ["oid"] } rsa.workspace = true +rusqlite.workspace = true scrypt = "0.11.0" sec1.workspace = true serde = "1.0.149" diff --git a/ext/node/lib.rs b/ext/node/lib.rs index dcaaa65886..aaf61797bb 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -657,6 +657,7 @@ deno_core::extension!(deno_node, "node:readline" = "readline.ts", "node:readline/promises" = "readline/promises.ts", "node:repl" = "repl.ts", + "node:sqlite" = "sqlite.ts", "node:stream" = "stream.ts", "node:stream/consumers" = "stream/consumers.mjs", "node:stream/promises" = "stream/promises.mjs", diff --git a/ext/node/ops/sqlite.rs b/ext/node/ops/sqlite.rs index f3df599dcb..6d69091693 100644 --- a/ext/node/ops/sqlite.rs +++ b/ext/node/ops/sqlite.rs @@ -1,14 +1,23 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::ops::Deref; +use std::cell::RefCell; use std::rc::Rc; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::v8; use deno_core::GarbageCollected; use serde::Deserialize; +#[derive(Debug, thiserror::Error)] +pub enum SqliteError { + #[error(transparent)] + SqliteError(#[from] rusqlite::Error), + #[error("Database is already in use")] + InUse, + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct DatabaseSyncOptions { @@ -16,7 +25,11 @@ struct DatabaseSyncOptions { enable_foreign_key_constraints: bool, } -pub struct DatabaseSync {} +pub struct DatabaseSync { + conn: Rc>>, + options: DatabaseSyncOptions, + location: String, +} impl GarbageCollected for DatabaseSync {} @@ -25,27 +38,128 @@ impl DatabaseSync { #[constructor] #[cppgc] fn new( - #[string] location: &str, + #[string] location: String, #[serde] options: DatabaseSyncOptions, - ) -> DatabaseSync { - DatabaseSync {} + ) -> Result { + let db = if options.open { + let db = rusqlite::Connection::open(&location)?; + if options.enable_foreign_key_constraints { + db.execute("PRAGMA foreign_keys = ON", [])?; + } + Some(db) + } else { + None + }; + + dbg!("statement typeid", std::any::TypeId::of::()); + Ok(DatabaseSync { + conn: Rc::new(RefCell::new(db)), + location, + options, + }) } #[fast] - fn open(&self) {} + fn open(&self) -> Result<(), SqliteError> { + let db = rusqlite::Connection::open(&self.location)?; + if self.options.enable_foreign_key_constraints { + db.execute("PRAGMA foreign_keys = ON", [])?; + } + + *self.conn.borrow_mut() = Some(db); + + Ok(()) + } #[fast] fn close(&self) {} #[cppgc] - fn prepare(&self, #[string] sql: &str) -> StatementSync { - StatementSync {} + fn prepare(&self, #[string] sql: &str) -> Result { + let db = self.conn.borrow(); + let db = db.as_ref().ok_or(SqliteError::InUse)?; + + let raw_handle = unsafe { db.handle() }; + + let mut raw_stmt = std::ptr::null_mut(); + let r = unsafe { + libsqlite3_sys::sqlite3_prepare_v2( + raw_handle, + sql.as_ptr() as *const i8, + sql.len() as i32, + &mut raw_stmt, + std::ptr::null_mut(), + ) + }; + + if r != libsqlite3_sys::SQLITE_OK { + panic!("Failed to prepare statement"); + } + + Ok(StatementSync { + inner: raw_stmt, + use_big_ints: false, + allow_bare_named_params: false, + db: self.conn.clone(), + }) } - // fn exec() <-- varargs + #[nofast] // divy will fix this dw + fn exec( + &self, + scope: &mut v8::HandleScope, + #[string] sql: &str, + #[varargs] params: Option<&v8::FunctionCallbackArguments>, + ) -> Result<(), SqliteError> { + let db = self.conn.borrow(); + let db = db.as_ref().ok_or(SqliteError::InUse)?; + + let mut stmt = db.prepare_cached(sql)?; + if let Some(params) = params { + bind(&mut stmt, scope, params, 1)?; + } + stmt.execute([])?; + + Ok(()) + } } -pub struct StatementSync {} +fn bind( + stmt: &mut rusqlite::Statement, + scope: &mut v8::HandleScope, + params: &v8::FunctionCallbackArguments, + offset: usize, +) -> Result<(), SqliteError> { + for index in offset..params.length() as usize { + let value = params.get(index as i32); + let index = (index + 1) - offset; + if value.is_null() { + // stmt.raw_bind_parameter(index, ())?; + } else if value.is_boolean() { + stmt.raw_bind_parameter(index, value.is_true())?; + } else if value.is_int32() { + stmt.raw_bind_parameter(index, value.integer_value(scope).unwrap())?; + } else if value.is_number() { + stmt.raw_bind_parameter(index, value.number_value(scope).unwrap())?; + } else if value.is_big_int() { + let bigint = value.to_big_int(scope).unwrap(); + let (value, _) = bigint.i64_value(); + stmt.raw_bind_parameter(index, value)?; + } else if value.is_string() { + stmt.raw_bind_parameter(index, value.to_rust_string_lossy(scope))?; + } + // TODO: Blobs + } + + Ok(()) +} + +pub struct StatementSync { + inner: *mut libsqlite3_sys::sqlite3_stmt, + use_big_ints: bool, + allow_bare_named_params: bool, + db: Rc>>, +} impl GarbageCollected for StatementSync {} @@ -54,10 +168,11 @@ impl StatementSync { #[constructor] #[cppgc] fn new(_: bool) -> StatementSync { - StatementSync {} + unimplemented!() } - // fn get() <-- varargs + #[fast] + fn get(&self, #[varargs] params: Option<&v8::FunctionCallbackArguments>) {} #[fast] fn run(&self) {} @@ -75,7 +190,18 @@ impl StatementSync { fn source_sql(&self) {} #[string] - fn expanded_sqlite(&self) -> String { - todo!() + fn expanded_sqlite(&self) -> Result { + let raw = unsafe { libsqlite3_sys::sqlite3_expanded_sql(self.inner) }; + if raw.is_null() { + // return Err(AnyError::msg("Failed to expand SQL")); + panic!("Failed to expand SQL"); + } + + let cstr = unsafe { std::ffi::CStr::from_ptr(raw) }; + let expanded_sql = cstr.to_string_lossy().to_string(); + + unsafe { libsqlite3_sys::sqlite3_free(raw as *mut std::ffi::c_void) }; + + Ok(expanded_sql) } } diff --git a/ext/node/polyfill.rs b/ext/node/polyfill.rs index a14b75bac0..7a5f32c45e 100644 --- a/ext/node/polyfill.rs +++ b/ext/node/polyfill.rs @@ -59,6 +59,7 @@ generate_builtin_node_module_lists! { "repl", "readline", "readline/promises", + "sqlite", "stream", "stream/consumers", "stream/promises", diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index df73cad6b7..9709487ad3 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -142,6 +142,7 @@ import querystring from "node:querystring"; import readline from "node:readline"; import readlinePromises from "node:readline/promises"; import repl from "node:repl"; +import sqlite from "node:sqlite"; import stream from "node:stream"; import streamConsumers from "node:stream/consumers"; import streamPromises from "node:stream/promises"; @@ -253,6 +254,7 @@ function setupBuiltinModules() { readline, "readline/promises": readlinePromises, repl, + sqlite, stream, "stream/consumers": streamConsumers, "stream/promises": streamPromises, diff --git a/ext/node/polyfills/sqlite.ts b/ext/node/polyfills/sqlite.ts new file mode 100644 index 0000000000..229c463a22 --- /dev/null +++ b/ext/node/polyfills/sqlite.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +import { DatabaseSync } from "ext:core/ops"; + +export { DatabaseSync }; + +export default { + DatabaseSync, +};