From 0ab878cda87cde41b7641be10b5c170f2f4eacaa Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 9 Dec 2024 12:03:01 +0530 Subject: [PATCH 01/21] init --- Cargo.lock | 4 +++ Cargo.toml | 3 ++ ext/node/lib.rs | 4 ++- ext/node/ops/mod.rs | 1 + ext/node/ops/sqlite.rs | 81 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 ext/node/ops/sqlite.rs diff --git a/Cargo.lock b/Cargo.lock index 9c42621100..fbecdb9155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9145,3 +9145,7 @@ dependencies = [ "cc", "pkg-config", ] + +[[patch.unused]] +name = "deno_core" +version = "0.325.0" diff --git a/Cargo.toml b/Cargo.toml index 4698acd06d..17b4a8ea7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -339,3 +339,6 @@ opt-level = 3 opt-level = 3 [profile.release.package.zstd-sys] opt-level = 3 + +[patch.crates-io] +deno_core = { path = "../deno_core/core" } diff --git a/ext/node/lib.rs b/ext/node/lib.rs index bf593ad432..dcaaa65886 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -431,7 +431,9 @@ deno_core::extension!(deno_node, ops::inspector::op_inspector_enabled, ], objects = [ - ops::perf_hooks::EldHistogram + ops::perf_hooks::EldHistogram, + ops::sqlite::DatabaseSync, + ops::sqlite::StatementSync ], esm_entry_point = "ext:deno_node/02_init.js", esm = [ diff --git a/ext/node/ops/mod.rs b/ext/node/ops/mod.rs index e5ea8b4172..2ecc8dfd75 100644 --- a/ext/node/ops/mod.rs +++ b/ext/node/ops/mod.rs @@ -13,6 +13,7 @@ pub mod os; pub mod perf_hooks; pub mod process; pub mod require; +pub mod sqlite; pub mod tls; pub mod util; pub mod v8; diff --git a/ext/node/ops/sqlite.rs b/ext/node/ops/sqlite.rs new file mode 100644 index 0000000000..f3df599dcb --- /dev/null +++ b/ext/node/ops/sqlite.rs @@ -0,0 +1,81 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::ops::Deref; +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(Deserialize)] +#[serde(rename_all = "camelCase")] +struct DatabaseSyncOptions { + open: bool, + enable_foreign_key_constraints: bool, +} + +pub struct DatabaseSync {} + +impl GarbageCollected for DatabaseSync {} + +#[op2] +impl DatabaseSync { + #[constructor] + #[cppgc] + fn new( + #[string] location: &str, + #[serde] options: DatabaseSyncOptions, + ) -> DatabaseSync { + DatabaseSync {} + } + + #[fast] + fn open(&self) {} + + #[fast] + fn close(&self) {} + + #[cppgc] + fn prepare(&self, #[string] sql: &str) -> StatementSync { + StatementSync {} + } + + // fn exec() <-- varargs +} + +pub struct StatementSync {} + +impl GarbageCollected for StatementSync {} + +#[op2] +impl StatementSync { + #[constructor] + #[cppgc] + fn new(_: bool) -> StatementSync { + StatementSync {} + } + + // fn get() <-- varargs + + #[fast] + fn run(&self) {} + + #[fast] + fn all(&self) {} + + #[fast] + fn set_allowed_bare_named_parameters(&self, enabled: bool) {} + + #[fast] + fn set_read_bigints(&self, enabled: bool) {} + + #[fast] + fn source_sql(&self) {} + + #[string] + fn expanded_sqlite(&self) -> String { + todo!() + } +} From 87de65525a4ec24d9e727c6dd9308ca44b5f8a99 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 10 Dec 2024 09:32:37 +0530 Subject: [PATCH 02/21] lets see --- Cargo.lock | 34 +++---- Cargo.toml | 2 +- ext/node/Cargo.toml | 2 + ext/node/lib.rs | 1 + ext/node/ops/sqlite.rs | 156 ++++++++++++++++++++++++++++--- ext/node/polyfill.rs | 1 + ext/node/polyfills/01_require.js | 2 + ext/node/polyfills/sqlite.ts | 9 ++ 8 files changed, 170 insertions(+), 37 deletions(-) create mode 100644 ext/node/polyfills/sqlite.ts 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, +}; From 72c96bf7d3e64075adff20b7dd19b0c25ae29886 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 10 Dec 2024 20:38:06 +0530 Subject: [PATCH 03/21] more --- Cargo.lock | 7 ++ ext/node/ops/sqlite.rs | 191 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcfd417f5d..4edb025373 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1500,6 +1500,7 @@ dependencies = [ "sourcemap 8.0.1", "static_assertions", "tokio", + "typeid", "url", "v8", "wasm_dep_analyzer", @@ -8072,6 +8073,12 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + [[package]] name = "typenum" version = "1.17.0" diff --git a/ext/node/ops/sqlite.rs b/ext/node/ops/sqlite.rs index 6d69091693..739b5625c1 100644 --- a/ext/node/ops/sqlite.rs +++ b/ext/node/ops/sqlite.rs @@ -51,7 +51,6 @@ impl DatabaseSync { None }; - dbg!("statement typeid", std::any::TypeId::of::()); Ok(DatabaseSync { conn: Rc::new(RefCell::new(db)), location, @@ -118,7 +117,7 @@ impl DatabaseSync { if let Some(params) = params { bind(&mut stmt, scope, params, 1)?; } - stmt.execute([])?; + stmt.raw_execute()?; Ok(()) } @@ -171,14 +170,176 @@ impl StatementSync { unimplemented!() } - #[fast] - fn get(&self, #[varargs] params: Option<&v8::FunctionCallbackArguments>) {} + fn get<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + #[varargs] params: Option<&v8::FunctionCallbackArguments>, + ) -> Result, SqliteError> { + let raw = self.inner; + + let result = v8::Object::new(scope); + unsafe { + libsqlite3_sys::sqlite3_reset(raw); + + let r = libsqlite3_sys::sqlite3_step(raw); + + if r == libsqlite3_sys::SQLITE_DONE { + return Ok(v8::Object::new(scope)); + } + if r != libsqlite3_sys::SQLITE_ROW { + // return Err(AnyError::msg("Failed to step statement")); + return panic!("Failed to step statement"); + } + + let columns = libsqlite3_sys::sqlite3_column_count(raw); + + for i in 0..columns { + let name = libsqlite3_sys::sqlite3_column_name(raw, i); + let name = std::ffi::CStr::from_ptr(name).to_string_lossy().to_string(); + let value = match libsqlite3_sys::sqlite3_column_type(raw, i) { + libsqlite3_sys::SQLITE_INTEGER => { + let value = libsqlite3_sys::sqlite3_column_int64(raw, i); + v8::Integer::new(scope, value as _).into() + } + libsqlite3_sys::SQLITE_FLOAT => { + let value = libsqlite3_sys::sqlite3_column_double(raw, i); + v8::Number::new(scope, value).into() + } + libsqlite3_sys::SQLITE_TEXT => { + let value = libsqlite3_sys::sqlite3_column_text(raw, i); + let value = std::ffi::CStr::from_ptr(value as _) + .to_string_lossy() + .to_string(); + v8::String::new_from_utf8( + scope, + value.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into() + } + libsqlite3_sys::SQLITE_BLOB => { + let value = libsqlite3_sys::sqlite3_column_blob(raw, i); + let size = libsqlite3_sys::sqlite3_column_bytes(raw, i); + let value = + std::slice::from_raw_parts(value as *const u8, size as usize); + let value = + v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) + .make_shared(); + v8::ArrayBuffer::with_backing_store(scope, &value).into() + } + libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), + _ => { + // return Err(AnyError::msg("Unknown column type")); + return panic!("Unknown column type"); + } + }; + + let name = v8::String::new_from_utf8( + scope, + name.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into(); + result.set(scope, name, value); + } + + libsqlite3_sys::sqlite3_reset(raw); + } + + Ok(result) + } #[fast] fn run(&self) {} - #[fast] - fn all(&self) {} + fn all<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + #[varargs] params: Option<&v8::FunctionCallbackArguments>, + ) -> Result, SqliteError> { + let raw = self.inner; + + let mut arr = vec![]; + unsafe { + libsqlite3_sys::sqlite3_reset(raw); + loop { + let result = v8::Object::new(scope); + + let r = libsqlite3_sys::sqlite3_step(raw); + if r == libsqlite3_sys::SQLITE_DONE { + break; + } + if r != libsqlite3_sys::SQLITE_ROW { + // return Err(AnyError::msg("Failed to step statement")); + return panic!("Failed to step statement"); + } + + let columns = libsqlite3_sys::sqlite3_column_count(raw); + + for i in 0..columns { + let name = libsqlite3_sys::sqlite3_column_name(raw, i); + let name = + std::ffi::CStr::from_ptr(name).to_string_lossy().to_string(); + let value = match libsqlite3_sys::sqlite3_column_type(raw, i) { + libsqlite3_sys::SQLITE_INTEGER => { + let value = libsqlite3_sys::sqlite3_column_int64(raw, i); + v8::Integer::new(scope, value as _).into() + } + libsqlite3_sys::SQLITE_FLOAT => { + let value = libsqlite3_sys::sqlite3_column_double(raw, i); + v8::Number::new(scope, value).into() + } + libsqlite3_sys::SQLITE_TEXT => { + let value = libsqlite3_sys::sqlite3_column_text(raw, i); + let value = std::ffi::CStr::from_ptr(value as _) + .to_string_lossy() + .to_string(); + v8::String::new_from_utf8( + scope, + value.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into() + } + libsqlite3_sys::SQLITE_BLOB => { + let value = libsqlite3_sys::sqlite3_column_blob(raw, i); + let size = libsqlite3_sys::sqlite3_column_bytes(raw, i); + let value = + std::slice::from_raw_parts(value as *const u8, size as usize); + let value = + v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) + .make_shared(); + v8::ArrayBuffer::with_backing_store(scope, &value).into() + } + libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), + _ => { + // return Err(AnyError::msg("Unknown column type")); + return panic!("Unknown column type"); + } + }; + + let name = v8::String::new_from_utf8( + scope, + name.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into(); + result.set(scope, name, value); + } + + arr.push(result.into()); + } + + libsqlite3_sys::sqlite3_reset(raw); + } + + let arr = v8::Array::new_with_elements(scope, &arr); + Ok(arr) + } #[fast] fn set_allowed_bare_named_parameters(&self, enabled: bool) {} @@ -186,11 +347,23 @@ impl StatementSync { #[fast] fn set_read_bigints(&self, enabled: bool) {} - #[fast] - fn source_sql(&self) {} + #[string] + fn source_sql(&self) -> Result { + let raw = unsafe { libsqlite3_sys::sqlite3_sql(self.inner) }; + + if raw.is_null() { + // return Err(AnyError::msg("Failed to get SQL")); + panic!("Failed to get SQL"); + } + + let cstr = unsafe { std::ffi::CStr::from_ptr(raw) }; + let sql = cstr.to_string_lossy().to_string(); + + Ok(sql) + } #[string] - fn expanded_sqlite(&self) -> Result { + fn expanded_sql(&self) -> Result { let raw = unsafe { libsqlite3_sys::sqlite3_expanded_sql(self.inner) }; if raw.is_null() { // return Err(AnyError::msg("Failed to expand SQL")); From 87574d241eff56a505073951d6c52953b3a4a45e Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 11 Dec 2024 19:17:37 +0530 Subject: [PATCH 04/21] changes --- ext/node/ops/sqlite/database.rs | 123 ++++++++++ ext/node/ops/sqlite/mod.rs | 21 ++ .../ops/{sqlite.rs => sqlite/statement.rs} | 217 ++++-------------- 3 files changed, 194 insertions(+), 167 deletions(-) create mode 100644 ext/node/ops/sqlite/database.rs create mode 100644 ext/node/ops/sqlite/mod.rs rename ext/node/ops/{sqlite.rs => sqlite/statement.rs} (58%) diff --git a/ext/node/ops/sqlite/database.rs b/ext/node/ops/sqlite/database.rs new file mode 100644 index 0000000000..80dc7e90a0 --- /dev/null +++ b/ext/node/ops/sqlite/database.rs @@ -0,0 +1,123 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::cell::RefCell; +use std::rc::Rc; + +use deno_core::op2; +use deno_core::GarbageCollected; +use serde::Deserialize; + +use super::SqliteError; +use super::StatementSync; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct DatabaseSyncOptions { + #[serde(default = "true_fn")] + open: bool, + #[serde(default = "true_fn")] + enable_foreign_key_constraints: bool, +} + +fn true_fn() -> bool { + true +} + +impl Default for DatabaseSyncOptions { + fn default() -> Self { + DatabaseSyncOptions { + open: true, + enable_foreign_key_constraints: true, + } + } +} + +pub struct DatabaseSync { + conn: Rc>>, + options: DatabaseSyncOptions, + location: String, +} + +impl GarbageCollected for DatabaseSync {} + +#[op2] +impl DatabaseSync { + #[constructor] + #[cppgc] + fn new( + #[string] location: String, + #[serde] options: Option, + ) -> Result { + let options = options.unwrap_or_default(); + + 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 + }; + + Ok(DatabaseSync { + conn: Rc::new(RefCell::new(db)), + location, + options, + }) + } + + #[fast] + 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) -> 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, + db: self.conn.clone(), + }) + } + + #[fast] + fn exec(&self, #[string] sql: &str) -> Result<(), SqliteError> { + let db = self.conn.borrow(); + let db = db.as_ref().ok_or(SqliteError::InUse)?; + + let mut stmt = db.prepare_cached(sql)?; + stmt.raw_execute()?; + + Ok(()) + } +} diff --git a/ext/node/ops/sqlite/mod.rs b/ext/node/ops/sqlite/mod.rs new file mode 100644 index 0000000000..7a3fae6f81 --- /dev/null +++ b/ext/node/ops/sqlite/mod.rs @@ -0,0 +1,21 @@ +mod database; +mod statement; + +pub use database::DatabaseSync; +pub use statement::StatementSync; + +#[derive(Debug, thiserror::Error)] +pub enum SqliteError { + #[error(transparent)] + SqliteError(#[from] rusqlite::Error), + #[error("Database is already in use")] + InUse, + #[error("Failed to step statement")] + FailedStep, + #[error("Unknown column type")] + UnknownColumnType, + #[error("Failed to get SQL")] + GetSqlFailed, + #[error(transparent)] + Other(deno_core::error::AnyError), +} diff --git a/ext/node/ops/sqlite.rs b/ext/node/ops/sqlite/statement.rs similarity index 58% rename from ext/node/ops/sqlite.rs rename to ext/node/ops/sqlite/statement.rs index 739b5625c1..1c09e777c6 100644 --- a/ext/node/ops/sqlite.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -6,158 +6,20 @@ use std::rc::Rc; use deno_core::op2; use deno_core::v8; use deno_core::GarbageCollected; -use serde::Deserialize; +use serde::Serialize; -#[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), -} +use super::SqliteError; -#[derive(Deserialize)] +#[derive(Serialize)] #[serde(rename_all = "camelCase")] -struct DatabaseSyncOptions { - open: bool, - enable_foreign_key_constraints: bool, -} - -pub struct DatabaseSync { - conn: Rc>>, - options: DatabaseSyncOptions, - location: String, -} - -impl GarbageCollected for DatabaseSync {} - -#[op2] -impl DatabaseSync { - #[constructor] - #[cppgc] - fn new( - #[string] location: String, - #[serde] options: DatabaseSyncOptions, - ) -> 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 - }; - - Ok(DatabaseSync { - conn: Rc::new(RefCell::new(db)), - location, - options, - }) - } - - #[fast] - 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) -> 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(), - }) - } - - #[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.raw_execute()?; - - Ok(()) - } -} - -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 RunStatementResult { + last_insert_rowid: i64, + changes: u64, } pub struct StatementSync { - inner: *mut libsqlite3_sys::sqlite3_stmt, - use_big_ints: bool, - allow_bare_named_params: bool, - db: Rc>>, + pub inner: *mut libsqlite3_sys::sqlite3_stmt, + pub db: Rc>>, } impl GarbageCollected for StatementSync {} @@ -187,8 +49,7 @@ impl StatementSync { return Ok(v8::Object::new(scope)); } if r != libsqlite3_sys::SQLITE_ROW { - // return Err(AnyError::msg("Failed to step statement")); - return panic!("Failed to step statement"); + return Err(SqliteError::FailedStep); } let columns = libsqlite3_sys::sqlite3_column_count(raw); @@ -230,8 +91,7 @@ impl StatementSync { } libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), _ => { - // return Err(AnyError::msg("Unknown column type")); - return panic!("Unknown column type"); + return Err(SqliteError::UnknownColumnType); } }; @@ -251,8 +111,41 @@ impl StatementSync { Ok(result) } - #[fast] - fn run(&self) {} + #[serde] + fn run( + &self, + scope: &mut v8::HandleScope, + #[varargs] params: Option<&v8::FunctionCallbackArguments>, + ) -> Result { + let raw = self.inner; + let db = self.db.borrow(); + let db = db.as_ref().unwrap(); + + let last_insert_rowid; + let changes; + + unsafe { + libsqlite3_sys::sqlite3_reset(raw); + + loop { + let r = libsqlite3_sys::sqlite3_step(raw); + if r == libsqlite3_sys::SQLITE_DONE { + break; + } + if r != libsqlite3_sys::SQLITE_ROW { + return Err(SqliteError::FailedStep); + } + } + + last_insert_rowid = db.last_insert_rowid(); + changes = db.changes(); + } + + Ok(RunStatementResult { + last_insert_rowid, + changes, + }) + } fn all<'a>( &self, @@ -272,8 +165,7 @@ impl StatementSync { break; } if r != libsqlite3_sys::SQLITE_ROW { - // return Err(AnyError::msg("Failed to step statement")); - return panic!("Failed to step statement"); + return Err(SqliteError::FailedStep); } let columns = libsqlite3_sys::sqlite3_column_count(raw); @@ -316,8 +208,7 @@ impl StatementSync { } libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), _ => { - // return Err(AnyError::msg("Unknown column type")); - return panic!("Unknown column type"); + return Err(SqliteError::UnknownColumnType); } }; @@ -341,19 +232,12 @@ impl StatementSync { Ok(arr) } - #[fast] - fn set_allowed_bare_named_parameters(&self, enabled: bool) {} - - #[fast] - fn set_read_bigints(&self, enabled: bool) {} - #[string] fn source_sql(&self) -> Result { let raw = unsafe { libsqlite3_sys::sqlite3_sql(self.inner) }; if raw.is_null() { - // return Err(AnyError::msg("Failed to get SQL")); - panic!("Failed to get SQL"); + return Err(SqliteError::GetSqlFailed); } let cstr = unsafe { std::ffi::CStr::from_ptr(raw) }; @@ -363,11 +247,10 @@ impl StatementSync { } #[string] - fn expanded_sql(&self) -> Result { + fn expanded_SQL(&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"); + return Err(SqliteError::GetSqlFailed); } let cstr = unsafe { std::ffi::CStr::from_ptr(raw) }; From 327993564743389205cdfdd0a2fe4beba3bc5edf Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 11 Dec 2024 22:55:37 +0530 Subject: [PATCH 05/21] in the mirrorrrr tsh tshh --- ext/node/ops/sqlite/database.rs | 55 +++++-- ext/node/ops/sqlite/mod.rs | 8 + ext/node/ops/sqlite/statement.rs | 273 ++++++++++++++----------------- 3 files changed, 169 insertions(+), 167 deletions(-) diff --git a/ext/node/ops/sqlite/database.rs b/ext/node/ops/sqlite/database.rs index 80dc7e90a0..7643ab154b 100644 --- a/ext/node/ops/sqlite/database.rs +++ b/ext/node/ops/sqlite/database.rs @@ -40,8 +40,15 @@ pub struct DatabaseSync { impl GarbageCollected for DatabaseSync {} +// Represents a single connection to a SQLite database. #[op2] impl DatabaseSync { + // Constructs a new `DatabaseSync` instance. + // + // A SQLite database can be stored in a file or in memory. To + // use a file-backed database, the `location` should be a path. + // To use an in-memory database, the `location` should be special + // name ":memory:". #[constructor] #[cppgc] fn new( @@ -67,8 +74,17 @@ impl DatabaseSync { }) } + // Opens the database specified by `location` of this instance. + // + // This method should only be used when the database is not opened + // via the constructor. An exception is thrown if the database is + // already opened. #[fast] fn open(&self) -> Result<(), SqliteError> { + if self.conn.borrow().is_some() { + return Err(SqliteError::AlreadyOpen); + } + let db = rusqlite::Connection::open(&self.location)?; if self.options.enable_foreign_key_constraints { db.execute("PRAGMA foreign_keys = ON", [])?; @@ -79,8 +95,32 @@ impl DatabaseSync { Ok(()) } + // Closes the database connection. An exception is thrown if the + // database is not open. #[fast] - fn close(&self) {} + fn close(&self) -> Result<(), SqliteError> { + if self.conn.borrow().is_none() { + return Err(SqliteError::AlreadyClosed); + } + + *self.conn.borrow_mut() = None; + Ok(()) + } + + // This method allows one or more SQL statements to be executed + // without returning any results. + // + // This method is a wrapper around sqlite3_exec(). + #[fast] + fn exec(&self, #[string] sql: &str) -> Result<(), SqliteError> { + let db = self.conn.borrow(); + let db = db.as_ref().ok_or(SqliteError::InUse)?; + + let mut stmt = db.prepare_cached(sql)?; + stmt.raw_execute()?; + + Ok(()) + } #[cppgc] fn prepare(&self, #[string] sql: &str) -> Result { @@ -101,7 +141,7 @@ impl DatabaseSync { }; if r != libsqlite3_sys::SQLITE_OK { - panic!("Failed to prepare statement"); + return Err(SqliteError::PrepareFailed); } Ok(StatementSync { @@ -109,15 +149,4 @@ impl DatabaseSync { db: self.conn.clone(), }) } - - #[fast] - fn exec(&self, #[string] sql: &str) -> Result<(), SqliteError> { - let db = self.conn.borrow(); - let db = db.as_ref().ok_or(SqliteError::InUse)?; - - let mut stmt = db.prepare_cached(sql)?; - stmt.raw_execute()?; - - Ok(()) - } } diff --git a/ext/node/ops/sqlite/mod.rs b/ext/node/ops/sqlite/mod.rs index 7a3fae6f81..110399ffb5 100644 --- a/ext/node/ops/sqlite/mod.rs +++ b/ext/node/ops/sqlite/mod.rs @@ -12,10 +12,18 @@ pub enum SqliteError { InUse, #[error("Failed to step statement")] FailedStep, + #[error("Failed to bind parameter. {0}")] + FailedBind(&'static str), #[error("Unknown column type")] UnknownColumnType, #[error("Failed to get SQL")] GetSqlFailed, + #[error("Database is already closed")] + AlreadyClosed, + #[error("Database is already open")] + AlreadyOpen, + #[error("Failed to prepare statement")] + PrepareFailed, #[error(transparent)] Other(deno_core::error::AnyError), } diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 1c09e777c6..157321815a 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -24,6 +24,117 @@ pub struct StatementSync { impl GarbageCollected for StatementSync {} +fn read_entry<'a>( + raw: *mut libsqlite3_sys::sqlite3_stmt, + scope: &mut v8::HandleScope<'a>, +) -> Result>, SqliteError> { + let result = v8::Object::new(scope); + unsafe { + let r = libsqlite3_sys::sqlite3_step(raw); + + if r == libsqlite3_sys::SQLITE_DONE { + return Ok(None); + } + if r != libsqlite3_sys::SQLITE_ROW { + return Err(SqliteError::FailedStep); + } + + let columns = libsqlite3_sys::sqlite3_column_count(raw); + + for i in 0..columns { + let name = libsqlite3_sys::sqlite3_column_name(raw, i); + let name = std::ffi::CStr::from_ptr(name).to_string_lossy().to_string(); + let value = match libsqlite3_sys::sqlite3_column_type(raw, i) { + libsqlite3_sys::SQLITE_INTEGER => { + let value = libsqlite3_sys::sqlite3_column_int64(raw, i); + v8::Integer::new(scope, value as _).into() + } + libsqlite3_sys::SQLITE_FLOAT => { + let value = libsqlite3_sys::sqlite3_column_double(raw, i); + v8::Number::new(scope, value).into() + } + libsqlite3_sys::SQLITE_TEXT => { + let value = libsqlite3_sys::sqlite3_column_text(raw, i); + let value = std::ffi::CStr::from_ptr(value as _) + .to_string_lossy() + .to_string(); + v8::String::new_from_utf8( + scope, + value.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into() + } + libsqlite3_sys::SQLITE_BLOB => { + let value = libsqlite3_sys::sqlite3_column_blob(raw, i); + let size = libsqlite3_sys::sqlite3_column_bytes(raw, i); + let value = + std::slice::from_raw_parts(value as *const u8, size as usize); + let value = + v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) + .make_shared(); + v8::ArrayBuffer::with_backing_store(scope, &value).into() + } + libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), + _ => { + return Err(SqliteError::UnknownColumnType); + } + }; + + let name = v8::String::new_from_utf8( + scope, + name.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into(); + result.set(scope, name, value); + } + } + + Ok(Some(result)) +} + +fn bind_params( + scope: &mut v8::HandleScope, + raw: *mut libsqlite3_sys::sqlite3_stmt, + params: Option<&v8::FunctionCallbackArguments>, +) -> Result<(), SqliteError> { + if let Some(params) = params { + let len = params.length(); + for i in 0..len { + let value = params.get(i); + + if value.is_number() { + let value = value.number_value(scope).unwrap(); + unsafe { + libsqlite3_sys::sqlite3_bind_double(raw, i as i32 + 1, value); + } + } else if value.is_string() { + let value = value.to_rust_string_lossy(scope); + unsafe { + libsqlite3_sys::sqlite3_bind_text( + raw, + i as i32 + 1, + value.as_ptr() as *const i8, + value.len() as i32, + libsqlite3_sys::SQLITE_TRANSIENT(), + ); + } + } else if value.is_null() { + unsafe { + libsqlite3_sys::sqlite3_bind_null(raw, i as i32 + 1); + } + } else { + return Err(SqliteError::FailedBind("Unsupported type")); + } + } + } + + Ok(()) +} + #[op2] impl StatementSync { #[constructor] @@ -39,72 +150,16 @@ impl StatementSync { ) -> Result, SqliteError> { let raw = self.inner; - let result = v8::Object::new(scope); unsafe { libsqlite3_sys::sqlite3_reset(raw); + } - let r = libsqlite3_sys::sqlite3_step(raw); + bind_params(scope, raw, params)?; - if r == libsqlite3_sys::SQLITE_DONE { - return Ok(v8::Object::new(scope)); - } - if r != libsqlite3_sys::SQLITE_ROW { - return Err(SqliteError::FailedStep); - } - - let columns = libsqlite3_sys::sqlite3_column_count(raw); - - for i in 0..columns { - let name = libsqlite3_sys::sqlite3_column_name(raw, i); - let name = std::ffi::CStr::from_ptr(name).to_string_lossy().to_string(); - let value = match libsqlite3_sys::sqlite3_column_type(raw, i) { - libsqlite3_sys::SQLITE_INTEGER => { - let value = libsqlite3_sys::sqlite3_column_int64(raw, i); - v8::Integer::new(scope, value as _).into() - } - libsqlite3_sys::SQLITE_FLOAT => { - let value = libsqlite3_sys::sqlite3_column_double(raw, i); - v8::Number::new(scope, value).into() - } - libsqlite3_sys::SQLITE_TEXT => { - let value = libsqlite3_sys::sqlite3_column_text(raw, i); - let value = std::ffi::CStr::from_ptr(value as _) - .to_string_lossy() - .to_string(); - v8::String::new_from_utf8( - scope, - value.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into() - } - libsqlite3_sys::SQLITE_BLOB => { - let value = libsqlite3_sys::sqlite3_column_blob(raw, i); - let size = libsqlite3_sys::sqlite3_column_bytes(raw, i); - let value = - std::slice::from_raw_parts(value as *const u8, size as usize); - let value = - v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) - .make_shared(); - v8::ArrayBuffer::with_backing_store(scope, &value).into() - } - libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), - _ => { - return Err(SqliteError::UnknownColumnType); - } - }; - - let name = v8::String::new_from_utf8( - scope, - name.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into(); - result.set(scope, name, value); - } + let result = read_entry(raw, scope) + .map(|r| r.unwrap_or_else(|| v8::Object::new(scope)))?; + unsafe { libsqlite3_sys::sqlite3_reset(raw); } @@ -127,6 +182,7 @@ impl StatementSync { unsafe { libsqlite3_sys::sqlite3_reset(raw); + bind_params(scope, raw, params)?; loop { let r = libsqlite3_sys::sqlite3_step(raw); if r == libsqlite3_sys::SQLITE_DONE { @@ -157,71 +213,9 @@ impl StatementSync { let mut arr = vec![]; unsafe { libsqlite3_sys::sqlite3_reset(raw); - loop { - let result = v8::Object::new(scope); - - let r = libsqlite3_sys::sqlite3_step(raw); - if r == libsqlite3_sys::SQLITE_DONE { - break; - } - if r != libsqlite3_sys::SQLITE_ROW { - return Err(SqliteError::FailedStep); - } - - let columns = libsqlite3_sys::sqlite3_column_count(raw); - - for i in 0..columns { - let name = libsqlite3_sys::sqlite3_column_name(raw, i); - let name = - std::ffi::CStr::from_ptr(name).to_string_lossy().to_string(); - let value = match libsqlite3_sys::sqlite3_column_type(raw, i) { - libsqlite3_sys::SQLITE_INTEGER => { - let value = libsqlite3_sys::sqlite3_column_int64(raw, i); - v8::Integer::new(scope, value as _).into() - } - libsqlite3_sys::SQLITE_FLOAT => { - let value = libsqlite3_sys::sqlite3_column_double(raw, i); - v8::Number::new(scope, value).into() - } - libsqlite3_sys::SQLITE_TEXT => { - let value = libsqlite3_sys::sqlite3_column_text(raw, i); - let value = std::ffi::CStr::from_ptr(value as _) - .to_string_lossy() - .to_string(); - v8::String::new_from_utf8( - scope, - value.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into() - } - libsqlite3_sys::SQLITE_BLOB => { - let value = libsqlite3_sys::sqlite3_column_blob(raw, i); - let size = libsqlite3_sys::sqlite3_column_bytes(raw, i); - let value = - std::slice::from_raw_parts(value as *const u8, size as usize); - let value = - v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) - .make_shared(); - v8::ArrayBuffer::with_backing_store(scope, &value).into() - } - libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), - _ => { - return Err(SqliteError::UnknownColumnType); - } - }; - - let name = v8::String::new_from_utf8( - scope, - name.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into(); - result.set(scope, name, value); - } + bind_params(scope, raw, params)?; + while let Some(result) = read_entry(raw, scope)? { arr.push(result.into()); } @@ -231,33 +225,4 @@ impl StatementSync { let arr = v8::Array::new_with_elements(scope, &arr); Ok(arr) } - - #[string] - fn source_sql(&self) -> Result { - let raw = unsafe { libsqlite3_sys::sqlite3_sql(self.inner) }; - - if raw.is_null() { - return Err(SqliteError::GetSqlFailed); - } - - let cstr = unsafe { std::ffi::CStr::from_ptr(raw) }; - let sql = cstr.to_string_lossy().to_string(); - - Ok(sql) - } - - #[string] - fn expanded_SQL(&self) -> Result { - let raw = unsafe { libsqlite3_sys::sqlite3_expanded_sql(self.inner) }; - if raw.is_null() { - return Err(SqliteError::GetSqlFailed); - } - - 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) - } } From bf6825e9fa854f68eaf5b027b2ae1c280c932841 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 12 Dec 2024 16:34:09 +0530 Subject: [PATCH 06/21] bench --- cli/bench/sqlite.js | 36 ++++ ext/node/ops/sqlite/mod.rs | 2 + ext/node/ops/sqlite/statement.rs | 325 +++++++++++++++++-------------- 3 files changed, 212 insertions(+), 151 deletions(-) create mode 100644 cli/bench/sqlite.js diff --git a/cli/bench/sqlite.js b/cli/bench/sqlite.js new file mode 100644 index 0000000000..d5f8aca4d6 --- /dev/null +++ b/cli/bench/sqlite.js @@ -0,0 +1,36 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file no-console + +import { DatabaseSync } from "node:sqlite"; +import fs from "node:fs"; + +function bench(name, fun, count = 10000) { + const start = Date.now(); + for (let i = 0; i < count; i++) fun(); + const elapsed = Date.now() - start; + const rate = Math.floor(count / (elapsed / 1000)); + console.log(` ${name}: time ${elapsed} ms rate ${rate}`); +} + +for (const name of [":memory:", "test.db"]) { + console.log(`Benchmarking ${name}`); + try { + fs.unlinkSync(name); + } catch (e) { + // Ignore + } + + const db = new DatabaseSync(name); + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)"); + + bench("prepare", () => db.prepare("SELECT * FROM test")); + bench("exec", () => db.exec("INSERT INTO test (name) VALUES ('foo')")); + + const stmt = db.prepare("SELECT * FROM test"); + bench("get", () => stmt.get()); + + const stmt2 = db.prepare("SELECT * FROM test WHERE id = ?"); + bench("get (integer bind)", () => stmt2.get(1)); + + bench("all", () => stmt.all(), 1000); +} diff --git a/ext/node/ops/sqlite/mod.rs b/ext/node/ops/sqlite/mod.rs index 110399ffb5..fe75c1fe85 100644 --- a/ext/node/ops/sqlite/mod.rs +++ b/ext/node/ops/sqlite/mod.rs @@ -24,6 +24,8 @@ pub enum SqliteError { AlreadyOpen, #[error("Failed to prepare statement")] PrepareFailed, + #[error("Invalid constructor")] + InvalidConstructor, #[error(transparent)] Other(deno_core::error::AnyError), } diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 157321815a..92026e97b9 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use deno_core::op2; use deno_core::v8; use deno_core::GarbageCollected; +use libsqlite3_sys as ffi; use serde::Serialize; use super::SqliteError; @@ -18,150 +19,194 @@ pub struct RunStatementResult { } pub struct StatementSync { - pub inner: *mut libsqlite3_sys::sqlite3_stmt, + pub inner: *mut ffi::sqlite3_stmt, pub db: Rc>>, } +impl Drop for StatementSync { + fn drop(&mut self) { + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // no other references to this pointer exist. + unsafe { + ffi::sqlite3_finalize(self.inner); + } + } +} + impl GarbageCollected for StatementSync {} -fn read_entry<'a>( - raw: *mut libsqlite3_sys::sqlite3_stmt, - scope: &mut v8::HandleScope<'a>, -) -> Result>, SqliteError> { - let result = v8::Object::new(scope); - unsafe { - let r = libsqlite3_sys::sqlite3_step(raw); - - if r == libsqlite3_sys::SQLITE_DONE { - return Ok(None); - } - if r != libsqlite3_sys::SQLITE_ROW { - return Err(SqliteError::FailedStep); - } - - let columns = libsqlite3_sys::sqlite3_column_count(raw); - - for i in 0..columns { - let name = libsqlite3_sys::sqlite3_column_name(raw, i); - let name = std::ffi::CStr::from_ptr(name).to_string_lossy().to_string(); - let value = match libsqlite3_sys::sqlite3_column_type(raw, i) { - libsqlite3_sys::SQLITE_INTEGER => { - let value = libsqlite3_sys::sqlite3_column_int64(raw, i); - v8::Integer::new(scope, value as _).into() - } - libsqlite3_sys::SQLITE_FLOAT => { - let value = libsqlite3_sys::sqlite3_column_double(raw, i); - v8::Number::new(scope, value).into() - } - libsqlite3_sys::SQLITE_TEXT => { - let value = libsqlite3_sys::sqlite3_column_text(raw, i); - let value = std::ffi::CStr::from_ptr(value as _) - .to_string_lossy() - .to_string(); - v8::String::new_from_utf8( - scope, - value.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into() - } - libsqlite3_sys::SQLITE_BLOB => { - let value = libsqlite3_sys::sqlite3_column_blob(raw, i); - let size = libsqlite3_sys::sqlite3_column_bytes(raw, i); - let value = - std::slice::from_raw_parts(value as *const u8, size as usize); - let value = - v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) - .make_shared(); - v8::ArrayBuffer::with_backing_store(scope, &value).into() - } - libsqlite3_sys::SQLITE_NULL => v8::null(scope).into(), - _ => { - return Err(SqliteError::UnknownColumnType); - } - }; - - let name = v8::String::new_from_utf8( - scope, - name.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into(); - result.set(scope, name, value); +impl StatementSync { + // Clear the prepared statement back to its initial state. + fn reset(&self) { + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + unsafe { + ffi::sqlite3_reset(self.inner); } } - Ok(Some(result)) -} - -fn bind_params( - scope: &mut v8::HandleScope, - raw: *mut libsqlite3_sys::sqlite3_stmt, - params: Option<&v8::FunctionCallbackArguments>, -) -> Result<(), SqliteError> { - if let Some(params) = params { - let len = params.length(); - for i in 0..len { - let value = params.get(i); - - if value.is_number() { - let value = value.number_value(scope).unwrap(); - unsafe { - libsqlite3_sys::sqlite3_bind_double(raw, i as i32 + 1, value); - } - } else if value.is_string() { - let value = value.to_rust_string_lossy(scope); - unsafe { - libsqlite3_sys::sqlite3_bind_text( - raw, - i as i32 + 1, - value.as_ptr() as *const i8, - value.len() as i32, - libsqlite3_sys::SQLITE_TRANSIENT(), - ); - } - } else if value.is_null() { - unsafe { - libsqlite3_sys::sqlite3_bind_null(raw, i as i32 + 1); - } - } else { - return Err(SqliteError::FailedBind("Unsupported type")); + // Evaluate the prepared statement. + fn step(&self) -> Result { + let raw = self.inner; + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + unsafe { + let r = ffi::sqlite3_step(raw); + if r == ffi::SQLITE_DONE { + return Ok(true); + } + if r != ffi::SQLITE_ROW { + return Err(SqliteError::FailedStep); } } + + Ok(false) } - Ok(()) + // Read the current row of the prepared statement. + fn read_row<'a>( + &self, + scope: &mut v8::HandleScope<'a>, + ) -> Result>, SqliteError> { + let result = v8::Object::new(scope); + let raw = self.inner; + unsafe { + if self.step()? { + return Ok(None); + } + + let columns = ffi::sqlite3_column_count(raw); + + for i in 0..columns { + let name = ffi::sqlite3_column_name(raw, i); + let name = std::ffi::CStr::from_ptr(name).to_string_lossy(); + let value = match ffi::sqlite3_column_type(raw, i) { + ffi::SQLITE_INTEGER => { + let value = ffi::sqlite3_column_int64(raw, i); + v8::Integer::new(scope, value as _).into() + } + ffi::SQLITE_FLOAT => { + let value = ffi::sqlite3_column_double(raw, i); + v8::Number::new(scope, value).into() + } + ffi::SQLITE_TEXT => { + let value = ffi::sqlite3_column_text(raw, i); + let value = std::ffi::CStr::from_ptr(value as _) + .to_string_lossy() + .to_string(); + v8::String::new_from_utf8( + scope, + value.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into() + } + ffi::SQLITE_BLOB => { + let value = ffi::sqlite3_column_blob(raw, i); + let size = ffi::sqlite3_column_bytes(raw, i); + let value = + std::slice::from_raw_parts(value as *const u8, size as usize); + let value = + v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) + .make_shared(); + v8::ArrayBuffer::with_backing_store(scope, &value).into() + } + ffi::SQLITE_NULL => v8::null(scope).into(), + _ => { + return Err(SqliteError::UnknownColumnType); + } + }; + + let name = v8::String::new_from_utf8( + scope, + name.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into(); + result.set(scope, name, value); + } + } + + Ok(Some(result)) + } + + fn bind_params( + &self, + scope: &mut v8::HandleScope, + params: Option<&v8::FunctionCallbackArguments>, + ) -> Result<(), SqliteError> { + let raw = self.inner; + + if let Some(params) = params { + let len = params.length(); + for i in 0..len { + let value = params.get(i); + + if value.is_number() { + let value = value.number_value(scope).unwrap(); + unsafe { + ffi::sqlite3_bind_double(raw, i as i32 + 1, value); + } + } else if value.is_string() { + let value = value.to_rust_string_lossy(scope); + unsafe { + ffi::sqlite3_bind_text( + raw, + i as i32 + 1, + value.as_ptr() as *const i8, + value.len() as i32, + ffi::SQLITE_TRANSIENT(), + ); + } + } else if value.is_null() { + unsafe { + ffi::sqlite3_bind_null(raw, i as i32 + 1); + } + } else if value.is_array_buffer_view() { + let value: v8::Local = value.try_into().unwrap(); + let data = value.data(); + let size = value.byte_length(); + + unsafe { + ffi::sqlite3_bind_blob( + raw, + i as i32 + 1, + data, + size as i32, + ffi::SQLITE_TRANSIENT(), + ); + } + } else { + return Err(SqliteError::FailedBind("Unsupported type")); + } + } + } + + Ok(()) + } } #[op2] impl StatementSync { #[constructor] #[cppgc] - fn new(_: bool) -> StatementSync { - unimplemented!() + fn new(_: bool) -> Result { + Err(SqliteError::InvalidConstructor) } fn get<'a>( &self, scope: &mut v8::HandleScope<'a>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, - ) -> Result, SqliteError> { - let raw = self.inner; + ) -> Result, SqliteError> { + self.bind_params(scope, params)?; - unsafe { - libsqlite3_sys::sqlite3_reset(raw); - } + let entry = self.read_row(scope)?; + let result = entry + .map(|r| r.into()) + .unwrap_or_else(|| v8::undefined(scope).into()); - bind_params(scope, raw, params)?; - - let result = read_entry(raw, scope) - .map(|r| r.unwrap_or_else(|| v8::Object::new(scope)))?; - - unsafe { - libsqlite3_sys::sqlite3_reset(raw); - } + self.reset(); Ok(result) } @@ -172,34 +217,17 @@ impl StatementSync { scope: &mut v8::HandleScope, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result { - let raw = self.inner; let db = self.db.borrow(); - let db = db.as_ref().unwrap(); + let db = db.as_ref().ok_or(SqliteError::InUse)?; - let last_insert_rowid; - let changes; + self.bind_params(scope, params)?; + self.step()?; - unsafe { - libsqlite3_sys::sqlite3_reset(raw); - - bind_params(scope, raw, params)?; - loop { - let r = libsqlite3_sys::sqlite3_step(raw); - if r == libsqlite3_sys::SQLITE_DONE { - break; - } - if r != libsqlite3_sys::SQLITE_ROW { - return Err(SqliteError::FailedStep); - } - } - - last_insert_rowid = db.last_insert_rowid(); - changes = db.changes(); - } + self.reset(); Ok(RunStatementResult { - last_insert_rowid, - changes, + last_insert_rowid: db.last_insert_rowid(), + changes: db.changes(), }) } @@ -208,20 +236,15 @@ impl StatementSync { scope: &mut v8::HandleScope<'a>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result, SqliteError> { - let raw = self.inner; - let mut arr = vec![]; - unsafe { - libsqlite3_sys::sqlite3_reset(raw); - bind_params(scope, raw, params)?; - while let Some(result) = read_entry(raw, scope)? { - arr.push(result.into()); - } - - libsqlite3_sys::sqlite3_reset(raw); + self.bind_params(scope, params)?; + while let Some(result) = self.read_row(scope)? { + arr.push(result.into()); } + self.reset(); + let arr = v8::Array::new_with_elements(scope, &arr); Ok(arr) } From 509bd954daf09df0ede6e733309cd7f5b07c82d3 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 14 Dec 2024 09:59:52 +0530 Subject: [PATCH 07/21] :sparkles: --- Cargo.lock | 25 ++++++++++++++----------- cli/Cargo.toml | 2 +- cli/lsp/tsc.rs | 1 + ext/fs/ops.rs | 1 + ext/node/ops/v8.rs | 7 ++++++- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4edb025373..605dae1955 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -668,6 +668,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf2a5fb3207c12b5d208ebc145f967fea5cac41a021c37417ccc31ba40f39ee" +[[package]] +name = "capacity_builder" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab97838e07d98bdddf1e7e191ffe3c718cba7477c0b6607fdfb96ecd696202df" +dependencies = [ + "itoa", +] + [[package]] name = "caseless" version = "0.2.1" @@ -1482,6 +1491,7 @@ dependencies = [ "bit-set", "bit-vec", "bytes", + "capacity_builder", "cooked-waker", "deno_core_icudata", "deno_ops", @@ -1500,7 +1510,6 @@ dependencies = [ "sourcemap 8.0.1", "static_assertions", "tokio", - "typeid", "url", "v8", "wasm_dep_analyzer", @@ -1564,9 +1573,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.161.2" +version = "0.161.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af787319136f3e7f73ef551c618aeec70794522e36cd75ae35132a3bad983ef" +checksum = "353a39c70d248af04600928cefc8066a9e4535fb6e7d7c518411e5efc822819f" dependencies = [ "anyhow", "cfg-if", @@ -4374,9 +4383,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jni-sys" @@ -8073,12 +8082,6 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" -[[package]] -name = "typeid" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" - [[package]] name = "typenum" version = "1.17.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 4b4b17292c..74ac159840 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -72,7 +72,7 @@ deno_ast = { workspace = true, features = ["bundler", "cjs", "codegen", "proposa deno_cache_dir.workspace = true deno_config.workspace = true deno_core = { workspace = true, features = ["include_js_files_for_snapshotting"] } -deno_doc = { version = "=0.161.2", features = ["rust", "comrak"] } +deno_doc = { version = "=0.161.3", features = ["rust", "comrak"] } deno_graph = { version = "=0.86.3" } deno_lint = { version = "=0.68.2", features = ["docs"] } deno_lockfile.workspace = true diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index d3d821ebb3..360e0946b2 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -4498,6 +4498,7 @@ impl<'a> ToV8<'a> for TscRequestArray { let method_name = deno_core::FastString::from_static(method_name) .v8_string(scope) + .unwrap() .into(); let args = args.unwrap_or_else(|| v8::Array::new(scope, 0).into()); let scope_url = serde_v8::to_v8(scope, self.scope) diff --git a/ext/fs/ops.rs b/ext/fs/ops.rs index 5e64585e0c..3c8696d14c 100644 --- a/ext/fs/ops.rs +++ b/ext/fs/ops.rs @@ -1424,6 +1424,7 @@ impl<'s> ToV8<'s> for V8MaybeStaticStr { Cow::Owned(value) => value.into(), } .v8_string(scope) + .unwrap() .into(), ) } diff --git a/ext/node/ops/v8.rs b/ext/node/ops/v8.rs index 61f67f11f7..8f09314d1d 100644 --- a/ext/node/ops/v8.rs +++ b/ext/node/ops/v8.rs @@ -68,6 +68,7 @@ impl v8::ValueSerializerImpl for SerializerDelegate { let obj = self.obj(scope); let key = FastString::from_static("_getSharedArrayBufferId") .v8_string(scope) + .unwrap() .into(); if let Some(v) = obj.get(scope, key) { if let Ok(fun) = v.try_cast::() { @@ -89,6 +90,7 @@ impl v8::ValueSerializerImpl for SerializerDelegate { let obj = self.obj(scope); let key = FastString::from_static("_getDataCloneError") .v8_string(scope) + .unwrap() .into(); if let Some(v) = obj.get(scope, key) { let fun = v @@ -112,6 +114,7 @@ impl v8::ValueSerializerImpl for SerializerDelegate { let obj = self.obj(scope); let key = FastString::from_static("_writeHostObject") .v8_string(scope) + .unwrap() .into(); if let Some(v) = obj.get(scope, key) { if let Ok(v) = v.try_cast::() { @@ -240,6 +243,7 @@ impl v8::ValueDeserializerImpl for DeserializerDelegate { let obj = v8::Local::new(scope, &self.obj); let key = FastString::from_static("_readHostObject") .v8_string(scope) + .unwrap() .into(); let scope = &mut v8::AllowJavascriptExecutionScope::new(scope); if let Some(v) = obj.get(scope, key) { @@ -250,7 +254,8 @@ impl v8::ValueDeserializerImpl for DeserializerDelegate { Err(_) => { let msg = FastString::from_static("readHostObject must return an object") - .v8_string(scope); + .v8_string(scope) + .unwrap(); let error = v8::Exception::type_error(scope, msg); scope.throw_exception(error); return None; From 11c4d61b864c232b88af4bef055cda194f62de19 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 14 Dec 2024 10:35:34 +0530 Subject: [PATCH 08/21] docs --- ext/node/ops/sqlite/database.rs | 8 ++ ext/node/ops/sqlite/statement.rs | 195 +++++++++++++++++++++---------- 2 files changed, 142 insertions(+), 61 deletions(-) diff --git a/ext/node/ops/sqlite/database.rs b/ext/node/ops/sqlite/database.rs index 7643ab154b..7326ad1382 100644 --- a/ext/node/ops/sqlite/database.rs +++ b/ext/node/ops/sqlite/database.rs @@ -122,14 +122,22 @@ impl DatabaseSync { Ok(()) } + // Compiles an SQL statement into a prepared statement. + // + // This method is a wrapper around `sqlite3_prepare_v2()`. #[cppgc] fn prepare(&self, #[string] sql: &str) -> Result { let db = self.conn.borrow(); let db = db.as_ref().ok_or(SqliteError::InUse)?; + // SAFETY: lifetime of the connection is guaranteed by reference + // counting. let raw_handle = unsafe { db.handle() }; let mut raw_stmt = std::ptr::null_mut(); + + // SAFETY: `sql` points to a valid memory location and its length + // is correct. let r = unsafe { libsqlite3_sys::sqlite3_prepare_v2( raw_handle, diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 92026e97b9..4312fdf3ce 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::cell::RefCell; use std::rc::Rc; @@ -33,6 +34,39 @@ impl Drop for StatementSync { } } +struct ColumnIterator<'a> { + stmt: &'a StatementSync, + index: i32, + count: i32, +} + +impl<'a> ColumnIterator<'a> { + fn new(stmt: &'a StatementSync) -> Self { + let count = stmt.column_count(); + ColumnIterator { + stmt, + index: 0, + count, + } + } +} + +impl<'a> Iterator for ColumnIterator<'a> { + type Item = (i32, Cow<'a, str>); + + fn next(&mut self) -> Option { + if self.index >= self.count { + return None; + } + + let index = self.index; + let name = self.stmt.column_name(self.index); + + self.index += 1; + Some((index, name)) + } +} + impl GarbageCollected for StatementSync {} impl StatementSync { @@ -61,75 +95,90 @@ impl StatementSync { Ok(false) } + fn column_count(&self) -> i32 { + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + unsafe { ffi::sqlite3_column_count(self.inner) } + } + + fn column_name(&self, index: i32) -> Cow { + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + unsafe { + let name = ffi::sqlite3_column_name(self.inner, index); + std::ffi::CStr::from_ptr(name as _).to_string_lossy() + } + } + + fn column_value<'a>( + &self, + index: i32, + scope: &mut v8::HandleScope<'a>, + ) -> v8::Local<'a, v8::Value> { + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + unsafe { + match ffi::sqlite3_column_type(self.inner, index) { + ffi::SQLITE_INTEGER => { + let value = ffi::sqlite3_column_int64(self.inner, index); + v8::Integer::new(scope, value as _).into() + } + ffi::SQLITE_FLOAT => { + let value = ffi::sqlite3_column_double(self.inner, index); + v8::Number::new(scope, value).into() + } + ffi::SQLITE_TEXT => { + let value = ffi::sqlite3_column_text(self.inner, index); + let value = std::ffi::CStr::from_ptr(value as _).to_string_lossy(); + v8::String::new_from_utf8( + scope, + value.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into() + } + ffi::SQLITE_BLOB => { + let value = ffi::sqlite3_column_blob(self.inner, index); + let size = ffi::sqlite3_column_bytes(self.inner, index); + let value = + std::slice::from_raw_parts(value as *const u8, size as usize); + let value = + v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) + .make_shared(); + v8::ArrayBuffer::with_backing_store(scope, &value).into() + } + ffi::SQLITE_NULL => v8::null(scope).into(), + _ => v8::undefined(scope).into(), + } + } + } + // Read the current row of the prepared statement. fn read_row<'a>( &self, scope: &mut v8::HandleScope<'a>, ) -> Result>, SqliteError> { + if self.step()? { + return Ok(None); + } + let result = v8::Object::new(scope); - let raw = self.inner; - unsafe { - if self.step()? { - return Ok(None); - } + let iter = ColumnIterator::new(self); - let columns = ffi::sqlite3_column_count(raw); - - for i in 0..columns { - let name = ffi::sqlite3_column_name(raw, i); - let name = std::ffi::CStr::from_ptr(name).to_string_lossy(); - let value = match ffi::sqlite3_column_type(raw, i) { - ffi::SQLITE_INTEGER => { - let value = ffi::sqlite3_column_int64(raw, i); - v8::Integer::new(scope, value as _).into() - } - ffi::SQLITE_FLOAT => { - let value = ffi::sqlite3_column_double(raw, i); - v8::Number::new(scope, value).into() - } - ffi::SQLITE_TEXT => { - let value = ffi::sqlite3_column_text(raw, i); - let value = std::ffi::CStr::from_ptr(value as _) - .to_string_lossy() - .to_string(); - v8::String::new_from_utf8( - scope, - value.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into() - } - ffi::SQLITE_BLOB => { - let value = ffi::sqlite3_column_blob(raw, i); - let size = ffi::sqlite3_column_bytes(raw, i); - let value = - std::slice::from_raw_parts(value as *const u8, size as usize); - let value = - v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) - .make_shared(); - v8::ArrayBuffer::with_backing_store(scope, &value).into() - } - ffi::SQLITE_NULL => v8::null(scope).into(), - _ => { - return Err(SqliteError::UnknownColumnType); - } - }; - - let name = v8::String::new_from_utf8( - scope, - name.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into(); - result.set(scope, name, value); - } + for (index, name) in iter { + let value = self.column_value(index, scope); + let name = v8::String::new_from_utf8( + scope, + name.as_bytes(), + v8::NewStringType::Normal, + ) + .unwrap() + .into(); + result.set(scope, name, value); } Ok(Some(result)) } + // Bind the parameters to the prepared statement. fn bind_params( &self, scope: &mut v8::HandleScope, @@ -144,33 +193,41 @@ impl StatementSync { if value.is_number() { let value = value.number_value(scope).unwrap(); + + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. unsafe { - ffi::sqlite3_bind_double(raw, i as i32 + 1, value); + ffi::sqlite3_bind_double(raw, i + 1, value); } } else if value.is_string() { let value = value.to_rust_string_lossy(scope); + + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data. unsafe { ffi::sqlite3_bind_text( raw, - i as i32 + 1, + i + 1, value.as_ptr() as *const i8, value.len() as i32, ffi::SQLITE_TRANSIENT(), ); } } else if value.is_null() { + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. unsafe { - ffi::sqlite3_bind_null(raw, i as i32 + 1); + ffi::sqlite3_bind_null(raw, i + 1); } } else if value.is_array_buffer_view() { let value: v8::Local = value.try_into().unwrap(); let data = value.data(); let size = value.byte_length(); + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data. unsafe { ffi::sqlite3_bind_blob( raw, - i as i32 + 1, + i + 1, data, size as i32, ffi::SQLITE_TRANSIENT(), @@ -186,6 +243,10 @@ impl StatementSync { } } +// Represents a single prepared statement. Cannot be initialized directly via constructor. +// Instances are created using `DatabaseSync#prepare`. +// +// A prepared statement is an efficient binary representation of the SQL used to create it. #[op2] impl StatementSync { #[constructor] @@ -194,6 +255,10 @@ impl StatementSync { Err(SqliteError::InvalidConstructor) } + // Executes a prepared statement and returns the first result as an object. + // + // The prepared statement does not return any results, this method returns undefined. + // Optionally, parameters can be bound to the prepared statement. fn get<'a>( &self, scope: &mut v8::HandleScope<'a>, @@ -211,6 +276,10 @@ impl StatementSync { Ok(result) } + // Executes a prepared statement and returns an object summarizing the resulting + // changes. + // + // Optionally, parameters can be bound to the prepared statement. #[serde] fn run( &self, @@ -231,6 +300,10 @@ impl StatementSync { }) } + // Executes a prepared statement and returns all results as an array of objects. + // + // If the prepared statement does not return any results, this method returns an empty array. + // Optionally, parameters can be bound to the prepared statement. fn all<'a>( &self, scope: &mut v8::HandleScope<'a>, From 57c29c326e576478a6d304e0922eb0048706205e Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 14 Dec 2024 10:36:38 +0530 Subject: [PATCH 09/21] use git --- Cargo.lock | 3 +++ Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 605dae1955..a61065a12d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1484,6 +1484,7 @@ dependencies = [ [[package]] name = "deno_core" version = "0.325.0" +source = "git+https://github.com/littledivy/deno_core?branch=rebuild_obj_templates#77c65eda54eede17223410996fd11eaf9a855d73" dependencies = [ "anyhow", "az", @@ -2054,6 +2055,7 @@ dependencies = [ [[package]] name = "deno_ops" version = "0.201.0" +source = "git+https://github.com/littledivy/deno_core?branch=rebuild_obj_templates#77c65eda54eede17223410996fd11eaf9a855d73" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -6694,6 +6696,7 @@ dependencies = [ [[package]] name = "serde_v8" version = "0.234.0" +source = "git+https://github.com/littledivy/deno_core?branch=rebuild_obj_templates#77c65eda54eede17223410996fd11eaf9a855d73" dependencies = [ "num-bigint", "serde", diff --git a/Cargo.toml b/Cargo.toml index d46b0cd927..62b05c6791 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -341,4 +341,4 @@ opt-level = 3 opt-level = 3 [patch.crates-io] -deno_core = { path = "../deno_core/core" } +deno_core = { git = "https://github.com/littledivy/deno_core", branch = "rebuild_obj_templates" } From 008738058fe707fc7965e0255f658dcf84e485ab Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 14 Dec 2024 13:16:15 +0530 Subject: [PATCH 10/21] setReadBigints --- ext/node/ops/sqlite/database.rs | 2 ++ ext/node/ops/sqlite/statement.rs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/node/ops/sqlite/database.rs b/ext/node/ops/sqlite/database.rs index 7326ad1382..3c0b81962f 100644 --- a/ext/node/ops/sqlite/database.rs +++ b/ext/node/ops/sqlite/database.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; @@ -155,6 +156,7 @@ impl DatabaseSync { Ok(StatementSync { inner: raw_stmt, db: self.conn.clone(), + use_big_ints: Cell::new(false), }) } } diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 4312fdf3ce..c579f65ccd 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use std::borrow::Cow; +use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; @@ -22,6 +23,8 @@ pub struct RunStatementResult { pub struct StatementSync { pub inner: *mut ffi::sqlite3_stmt, pub db: Rc>>, + + pub use_big_ints: Cell, } impl Drop for StatementSync { @@ -118,7 +121,11 @@ impl StatementSync { match ffi::sqlite3_column_type(self.inner, index) { ffi::SQLITE_INTEGER => { let value = ffi::sqlite3_column_int64(self.inner, index); - v8::Integer::new(scope, value as _).into() + if self.use_big_ints.get() { + v8::BigInt::new_from_i64(scope, value).into() + } else { + v8::Integer::new(scope, value as _).into() + } } ffi::SQLITE_FLOAT => { let value = ffi::sqlite3_column_double(self.inner, index); @@ -321,4 +328,9 @@ impl StatementSync { let arr = v8::Array::new_with_elements(scope, &arr); Ok(arr) } + + #[fast] + fn set_read_big_ints(&self, enabled: bool) { + self.use_big_ints.set(enabled); + } } From 9e7306ac7f85f8c7263d6cfcdb68d181ce04f1fa Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 08:50:49 +0530 Subject: [PATCH 11/21] core --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b7fbc0ba0..49adaac430 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1484,7 +1484,7 @@ dependencies = [ [[package]] name = "deno_core" version = "0.325.0" -source = "git+https://github.com/littledivy/deno_core?branch=rebuild_obj_templates#77c65eda54eede17223410996fd11eaf9a855d73" +source = "git+https://github.com/denoland/deno_core?rev=f58a6f9#f58a6f98355fa61d3847af7962971eaf376515ff" dependencies = [ "anyhow", "az", @@ -2055,7 +2055,7 @@ dependencies = [ [[package]] name = "deno_ops" version = "0.201.0" -source = "git+https://github.com/littledivy/deno_core?branch=rebuild_obj_templates#77c65eda54eede17223410996fd11eaf9a855d73" +source = "git+https://github.com/denoland/deno_core?rev=f58a6f9#f58a6f98355fa61d3847af7962971eaf376515ff" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -6696,7 +6696,7 @@ dependencies = [ [[package]] name = "serde_v8" version = "0.234.0" -source = "git+https://github.com/littledivy/deno_core?branch=rebuild_obj_templates#77c65eda54eede17223410996fd11eaf9a855d73" +source = "git+https://github.com/denoland/deno_core?rev=f58a6f9#f58a6f98355fa61d3847af7962971eaf376515ff" dependencies = [ "num-bigint", "serde", diff --git a/Cargo.toml b/Cargo.toml index ae81662d72..cd2fdc0bcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -342,4 +342,4 @@ opt-level = 3 opt-level = 3 [patch.crates-io] -deno_core = { git = "https://github.com/littledivy/deno_core", branch = "rebuild_obj_templates" } +deno_core = { git = "https://github.com/denoland/deno_core", rev = "f58a6f9" } From 9e37da438c73b223cf7bc41ebed9a76b30dc0d57 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 08:58:30 +0530 Subject: [PATCH 12/21] lint ks --- cli/bench/sqlite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/bench/sqlite.js b/cli/bench/sqlite.js index d5f8aca4d6..c87dfe47e8 100644 --- a/cli/bench/sqlite.js +++ b/cli/bench/sqlite.js @@ -16,7 +16,7 @@ for (const name of [":memory:", "test.db"]) { console.log(`Benchmarking ${name}`); try { fs.unlinkSync(name); - } catch (e) { + } catch { // Ignore } From d104b0b8b37ce50c297b40d829c437f171a01c58 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 09:04:17 +0530 Subject: [PATCH 13/21] copyright header --- ext/node/ops/sqlite/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/node/ops/sqlite/mod.rs b/ext/node/ops/sqlite/mod.rs index fe75c1fe85..fd786010ca 100644 --- a/ext/node/ops/sqlite/mod.rs +++ b/ext/node/ops/sqlite/mod.rs @@ -1,3 +1,5 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + mod database; mod statement; From a9f85a70e246527b76518d777a7fe7e36a18c9e1 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 09:12:02 +0530 Subject: [PATCH 14/21] aarch64 ptr type --- ext/node/ops/sqlite/database.rs | 2 +- ext/node/ops/sqlite/statement.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/node/ops/sqlite/database.rs b/ext/node/ops/sqlite/database.rs index 3c0b81962f..c39aebcff6 100644 --- a/ext/node/ops/sqlite/database.rs +++ b/ext/node/ops/sqlite/database.rs @@ -142,7 +142,7 @@ impl DatabaseSync { let r = unsafe { libsqlite3_sys::sqlite3_prepare_v2( raw_handle, - sql.as_ptr() as *const i8, + sql.as_ptr() as *const _, sql.len() as i32, &mut raw_stmt, std::ptr::null_mut(), diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index c579f65ccd..0a0e65e7d7 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -214,7 +214,7 @@ impl StatementSync { ffi::sqlite3_bind_text( raw, i + 1, - value.as_ptr() as *const i8, + value.as_ptr() as *const _, value.len() as i32, ffi::SQLITE_TRANSIENT(), ); From e21982d159822ce3eba94a4b2a50779edeeea4d2 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 12:02:03 +0530 Subject: [PATCH 15/21] perf --- ext/node/ops/sqlite/statement.rs | 43 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 0a0e65e7d7..295ba77265 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -52,10 +52,14 @@ impl<'a> ColumnIterator<'a> { count, } } + + fn column_count(&self) -> usize { + self.count as usize + } } impl<'a> Iterator for ColumnIterator<'a> { - type Item = (i32, Cow<'a, str>); + type Item = (i32, &'a [u8]); fn next(&mut self) -> Option { if self.index >= self.count { @@ -103,11 +107,11 @@ impl StatementSync { unsafe { ffi::sqlite3_column_count(self.inner) } } - fn column_name(&self, index: i32) -> Cow { + fn column_name(&self, index: i32) -> &[u8] { // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. unsafe { let name = ffi::sqlite3_column_name(self.inner, index); - std::ffi::CStr::from_ptr(name as _).to_string_lossy() + std::ffi::CStr::from_ptr(name as _).to_bytes() } } @@ -133,10 +137,10 @@ impl StatementSync { } ffi::SQLITE_TEXT => { let value = ffi::sqlite3_column_text(self.inner, index); - let value = std::ffi::CStr::from_ptr(value as _).to_string_lossy(); + let value = std::ffi::CStr::from_ptr(value as _); v8::String::new_from_utf8( scope, - value.as_bytes(), + value.to_bytes(), v8::NewStringType::Normal, ) .unwrap() @@ -167,21 +171,28 @@ impl StatementSync { return Ok(None); } - let result = v8::Object::new(scope); let iter = ColumnIterator::new(self); + let num_cols = iter.column_count(); + + let mut names = Vec::with_capacity(num_cols); + let mut values = Vec::with_capacity(num_cols); + for (index, name) in iter { let value = self.column_value(index, scope); - let name = v8::String::new_from_utf8( - scope, - name.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap() - .into(); - result.set(scope, name, value); + let name = + v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal) + .unwrap() + .into(); + + names.push(name); + values.push(value); } + let null = v8::null(scope).into(); + let result = + v8::Object::with_prototype_and_properties(scope, null, &names, &values); + Ok(Some(result)) } @@ -271,6 +282,8 @@ impl StatementSync { scope: &mut v8::HandleScope<'a>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result, SqliteError> { + self.reset(); + self.bind_params(scope, params)?; let entry = self.read_row(scope)?; @@ -278,8 +291,6 @@ impl StatementSync { .map(|r| r.into()) .unwrap_or_else(|| v8::undefined(scope).into()); - self.reset(); - Ok(result) } From 574892aa020b20c7229f83df5479b03863ac8b80 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 12:02:50 +0530 Subject: [PATCH 16/21] update test --- tests/integration/lsp_tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 13a3c0d69b..e21157ebc6 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -8463,6 +8463,7 @@ fn lsp_completions_node_specifier() { "node:repl", "node:readline", "node:readline/promises", + "node:sqlite", "node:stream", "node:stream/consumers", "node:stream/promises", From f0409a61227555bfa6696202990aa2869ce0fae3 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 12:07:19 +0530 Subject: [PATCH 17/21] lint --- ext/node/ops/sqlite/statement.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 295ba77265..78029ae6ba 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use std::borrow::Cow; use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; From 636a6c3265ecfa68478592c58a3b824ed4cce49c Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 15 Dec 2024 18:22:25 +0530 Subject: [PATCH 18/21] deno_core 0.326.0 --- Cargo.lock | 15 +++++++++------ Cargo.toml | 5 +---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49adaac430..5d3ec21962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1483,8 +1483,9 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.325.0" -source = "git+https://github.com/denoland/deno_core?rev=f58a6f9#f58a6f98355fa61d3847af7962971eaf376515ff" +version = "0.326.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed157162dc5320a2b46ffeeaec24788339df0f2437cfaea78a8d82696715ad7f" dependencies = [ "anyhow", "az", @@ -2054,8 +2055,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.201.0" -source = "git+https://github.com/denoland/deno_core?rev=f58a6f9#f58a6f98355fa61d3847af7962971eaf376515ff" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd8ac1af251e292388e516dd339b9a3b982a6d1e7f8644c08e34671ca39003c" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -6695,8 +6697,9 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.234.0" -source = "git+https://github.com/denoland/deno_core?rev=f58a6f9#f58a6f98355fa61d3847af7962971eaf376515ff" +version = "0.235.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07afd8b67b4a442ecc2823038473ac0e9e5682de93c213323b60661afdd7eb4" dependencies = [ "num-bigint", "serde", diff --git a/Cargo.toml b/Cargo.toml index cd2fdc0bcc..984cb187ef 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.325.0" } +deno_core = { version = "0.326.0" } deno_bench_util = { version = "0.176.0", path = "./bench_util" } deno_config = { version = "=0.39.3", features = ["workspace", "sync"] } @@ -340,6 +340,3 @@ opt-level = 3 opt-level = 3 [profile.release.package.zstd-sys] opt-level = 3 - -[patch.crates-io] -deno_core = { git = "https://github.com/denoland/deno_core", rev = "f58a6f9" } From a43ea7ae195a38f957c6e8af95bc3a478b7409bd Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 16 Dec 2024 09:47:42 +0530 Subject: [PATCH 19/21] update comments --- ext/node/ops/sqlite/statement.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 78029ae6ba..4f93114e4e 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -78,7 +78,8 @@ impl GarbageCollected for StatementSync {} impl StatementSync { // Clear the prepared statement back to its initial state. fn reset(&self) { - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { ffi::sqlite3_reset(self.inner); } @@ -87,7 +88,8 @@ impl StatementSync { // Evaluate the prepared statement. fn step(&self) -> Result { let raw = self.inner; - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { let r = ffi::sqlite3_step(raw); if r == ffi::SQLITE_DONE { @@ -102,12 +104,14 @@ impl StatementSync { } fn column_count(&self) -> i32 { - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { ffi::sqlite3_column_count(self.inner) } } fn column_name(&self, index: i32) -> &[u8] { - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { let name = ffi::sqlite3_column_name(self.inner, index); std::ffi::CStr::from_ptr(name as _).to_bytes() @@ -119,7 +123,8 @@ impl StatementSync { index: i32, scope: &mut v8::HandleScope<'a>, ) -> v8::Local<'a, v8::Value> { - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { match ffi::sqlite3_column_type(self.inner, index) { ffi::SQLITE_INTEGER => { @@ -211,14 +216,17 @@ impl StatementSync { if value.is_number() { let value = value.number_value(scope).unwrap(); - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { ffi::sqlite3_bind_double(raw, i + 1, value); } } else if value.is_string() { let value = value.to_rust_string_lossy(scope); - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. + // // SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data. unsafe { ffi::sqlite3_bind_text( @@ -230,7 +238,8 @@ impl StatementSync { ); } } else if value.is_null() { - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. unsafe { ffi::sqlite3_bind_null(raw, i + 1); } @@ -239,7 +248,9 @@ impl StatementSync { let data = value.data(); let size = value.byte_length(); - // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt. + // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt + // as it lives as long as the StatementSync instance. + // // SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data. unsafe { ffi::sqlite3_bind_blob( From 0c0a3e6505e168129aacc3e172dd4d3d022abcd8 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 16 Dec 2024 11:29:43 +0530 Subject: [PATCH 20/21] add tests --- tests/integration/node_unit_tests.rs | 1 + tests/unit_node/sqlite_test.ts | 66 ++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/unit_node/sqlite_test.ts diff --git a/tests/integration/node_unit_tests.rs b/tests/integration/node_unit_tests.rs index 9cb1af9496..7da1ae20a8 100644 --- a/tests/integration/node_unit_tests.rs +++ b/tests/integration/node_unit_tests.rs @@ -89,6 +89,7 @@ util::unit_test_factory!( querystring_test, readline_test, repl_test, + sqlite_test, stream_test, string_decoder_test, timers_test, diff --git a/tests/unit_node/sqlite_test.ts b/tests/unit_node/sqlite_test.ts new file mode 100644 index 0000000000..8199968e61 --- /dev/null +++ b/tests/unit_node/sqlite_test.ts @@ -0,0 +1,66 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { DatabaseSync } from "node:sqlite"; +import { assertEquals, assertThrows } from "@std/assert"; + +Deno.test("[node/sqlite] in-memory databases", () => { + const db1 = new DatabaseSync(":memory:"); + const db2 = new DatabaseSync(":memory:"); + db1.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);"); + db1.exec("INSERT INTO data (key) VALUES (1);"); + + db2.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);"); + db2.exec("INSERT INTO data (key) VALUES (1);"); + + assertEquals(db1.prepare("SELECT * FROM data").all(), [{ + key: 1, + __proto__: null, + }]); + assertEquals(db2.prepare("SELECT * FROM data").all(), [{ + key: 1, + __proto__: null, + }]); +}); + +Deno.test("[node/sqlite] Errors originating from SQLite should be thrown", () => { + const db = new DatabaseSync(":memory:"); + db.exec(` + CREATE TABLE test( + key INTEGER PRIMARY KEY + ) STRICT; + `); + const stmt = db.prepare("INSERT INTO test(key) VALUES(?)"); + assertEquals(stmt.run(1), { lastInsertRowid: 1, changes: 1 }); + + assertThrows(() => stmt.run(1), Error); +}); + +Deno.test( + { + permissions: { read: true, write: true }, + name: "[node/sqlite] PRAGMAs are supported", + }, + () => { + const tempDir = Deno.makeTempDirSync(); + const db = new DatabaseSync(`${tempDir}/test.db`); + + assertEquals(db.prepare("PRAGMA journal_mode = WAL").get(), { + journal_mode: "wal", + __proto__: null, + }); + + db.close(); + Deno.removeSync(tempDir, { recursive: true }); + }, +); + +Deno.test("[node/sqlite] StatementSync read bigints are supported", () => { + const db = new DatabaseSync(":memory:"); + db.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);"); + db.exec("INSERT INTO data (key) VALUES (1);"); + + const stmt = db.prepare("SELECT * FROM data"); + assertEquals(stmt.get(), { key: 1, __proto__: null }); + + stmt.setReadBigInts(true); + assertEquals(stmt.get(), { key: 1n, __proto__: null }); +}); From f9654b35d65dc86cff98703e78ca5871ebb1aac0 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 16 Dec 2024 21:18:32 +0530 Subject: [PATCH 21/21] huh --- tests/unit_node/sqlite_test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit_node/sqlite_test.ts b/tests/unit_node/sqlite_test.ts index 8199968e61..a3b93e66ed 100644 --- a/tests/unit_node/sqlite_test.ts +++ b/tests/unit_node/sqlite_test.ts @@ -49,7 +49,6 @@ Deno.test( }); db.close(); - Deno.removeSync(tempDir, { recursive: true }); }, );