/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node_sqlite/statement.rs
1 191 строка
37 KB
Nathan Whitaker
fix(ext/node): handle sqlite conversion failures (#36024)
14 июл 2026, 15:18
Не верифицирован
14 июл 2026, 15:18
6a9d607
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; use deno_core::GarbageCollected; use deno_core::ToV8; use deno_core::op2; use deno_core::v8; use deno_core::v8::GetPropertyNamesArgs; use deno_core::v8_static_strings; use rusqlite::ffi; use super::SqliteError; use super::validators; // ECMA-262, 15th edition, 21.1.2.6. Number.MAX_SAFE_INTEGER (2^53-1) const MAX_SAFE_JS_INTEGER: i64 = 9007199254740991; pub struct RunStatementResult { last_insert_rowid: i64, changes: u64, use_big_ints: bool, } impl<'a> ToV8<'a> for RunStatementResult { type Error = SqliteError; fn to_v8( self, scope: &mut v8::PinScope<'a, '_>, ) -> Result<v8::Local<'a, v8::Value>, SqliteError> { v8_static_strings! { LAST_INSERT_ROW_ID = "lastInsertRowid", CHANGES = "changes", } let obj = v8::Object::new(scope); let last_insert_row_id_str = LAST_INSERT_ROW_ID.v8_string(scope).unwrap(); let last_insert_row_id = if self.use_big_ints { v8::BigInt::new_from_i64(scope, self.last_insert_rowid).into() } else { v8::Number::new(scope, self.last_insert_rowid as f64).into() }; // create_data_property defines own data properties directly, avoiding the // generic [[Set]] runtime path (property-key lookup + hidden-class // transitions) that showed up as ~half the cost of a `run()` in profiles. obj .create_data_property( scope, last_insert_row_id_str.into(), last_insert_row_id, ) .unwrap(); let changes_str = CHANGES.v8_string(scope).unwrap(); let changes = if self.use_big_ints { v8::BigInt::new_from_u64(scope, self.changes).into() } else { v8::Number::new(scope, self.changes as f64).into() }; obj .create_data_property(scope, changes_str.into(), changes) .unwrap(); Ok(obj.into()) } } pub type InnerStatementPtr = Rc<Cell<Option<*mut ffi::sqlite3_stmt>>>; pub trait StatementExecution { fn stmt_ptr(&self) -> Result<*mut ffi::sqlite3_stmt, SqliteError>; fn step(&self) -> Result<bool, SqliteError>; fn return_arrays(&self) -> bool; fn use_big_ints(&self) -> bool; fn check_bind_result(&self, r: i32) -> Result<(), SqliteError>; fn column_count(&self) -> Result<i32, SqliteError> { let raw = self.stmt_ptr()?; // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the StatementSync instance. let count = unsafe { ffi::sqlite3_column_count(raw) }; Ok(count) } fn column_name(&self, index: i32) -> Result<&[u8], SqliteError> { let raw = self.stmt_ptr()?; // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the StatementSync instance. unsafe { let name = ffi::sqlite3_column_name(raw, index); Ok(std::ffi::CStr::from_ptr(name as _).to_bytes()) } } fn column_value<'a>( &self, index: i32, scope: &mut v8::PinScope<'a, '_>, ) -> Result<v8::Local<'a, v8::Value>, SqliteError> { let raw = self.stmt_ptr()?; let use_big_ints = self.use_big_ints(); // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the statement instance. unsafe { Ok(match ffi::sqlite3_column_type(raw, index) { ffi::SQLITE_INTEGER => { let value = ffi::sqlite3_column_int64(raw, index); if use_big_ints { v8::BigInt::new_from_i64(scope, value).into() } else if value.abs() <= MAX_SAFE_JS_INTEGER { v8::Number::new(scope, value as f64).into() } else { return Err(SqliteError::NumberTooLarge(value)); } } ffi::SQLITE_FLOAT => { let value = ffi::sqlite3_column_double(raw, index); v8::Number::new(scope, value).into() } ffi::SQLITE_TEXT => { let value = ffi::sqlite3_column_text(raw, index); let value = std::ffi::CStr::from_ptr(value as _); v8::String::new_from_utf8( scope, value.to_bytes(), v8::NewStringType::Normal, ) .unwrap() .into() } ffi::SQLITE_BLOB => { let value = ffi::sqlite3_column_blob(raw, index); let size = ffi::sqlite3_column_bytes(raw, index); let ab = if size == 0 { v8::ArrayBuffer::new(scope, 0) } else { let value = std::slice::from_raw_parts(value as *const u8, size as usize); let bs = v8::ArrayBuffer::new_backing_store_from_vec(value.to_vec()) .make_shared(); v8::ArrayBuffer::with_backing_store(scope, &bs) }; v8::Uint8Array::new(scope, ab, 0, size as _).unwrap().into() } ffi::SQLITE_NULL => v8::null(scope).into(), _ => v8::undefined(scope).into(), }) } } fn bind_value( &self, scope: &mut v8::PinScope<'_, '_>, value: v8::Local<v8::Value>, index: i32, ) -> Result<(), SqliteError> { let raw = self.stmt_ptr()?; let r = if value.is_number() { let value = value.number_value(scope).unwrap(); // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the statement instance. unsafe { ffi::sqlite3_bind_double(raw, index, value) } } else if value.is_string() { let value = value.to_rust_string_lossy(scope); // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the statement instance. // // SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data. unsafe { ffi::sqlite3_bind_text( raw, index, value.as_ptr() as *const _, value.len() as i32, ffi::SQLITE_TRANSIENT(), ) } } else if value.is_null() { // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the statement instance. unsafe { ffi::sqlite3_bind_null(raw, index) } } else if value.is_array_buffer_view() { let value: v8::Local<v8::ArrayBufferView> = value.try_into().unwrap(); let mut data = value.data(); let mut size = value.byte_length(); // data may be NULL if length is 0 or ab is detached. we need to pass a valid pointer // to sqlite3_bind_blob, so we use a static empty array in this case. if data.is_null() { static EMPTY: [u8; 0] = []; data = EMPTY.as_ptr() as *mut _; size = 0; } // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the statement instance. // // SQLITE_TRANSIENT is used to indicate that SQLite should make a copy of the data. unsafe { ffi::sqlite3_bind_blob( raw, index, data, size as i32, ffi::SQLITE_TRANSIENT(), ) } } else if value.is_big_int() { let value: v8::Local<v8::BigInt> = value.try_into().unwrap(); let (as_int, lossless) = value.i64_value(); if !lossless { return Err(SqliteError::InvalidBindValue( "BigInt value is too large to bind", )); } // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the statement instance. unsafe { ffi::sqlite3_bind_int64(raw, index, as_int) } } else { return Err(SqliteError::InvalidBindType(index)); }; self.check_bind_result(r) } fn read_row<'a>( &self, scope: &mut v8::PinScope<'a, '_>, ) -> Result<Option<v8::Local<'a, v8::Value>>, SqliteError> { if self.step()? { return Ok(None); } let num_cols = self.column_count()?; let mut names = Vec::with_capacity(num_cols as usize); let mut values = Vec::with_capacity(num_cols as usize); for i in 0..num_cols { let name = self.column_name(i)?; let value = self.column_value(i, scope)?; let name = v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal) .unwrap() .into(); names.push(name); values.push(value); } if self.return_arrays() { let result = v8::Array::new_with_elements(scope, &values); Ok(Some(result.into())) } else { let null = v8::null(scope).into(); let result = v8::Object::with_prototype_and_properties(scope, null, &names, &values); Ok(Some(result.into())) } } } #[derive(Debug)] pub struct StatementSync { pub inner: InnerStatementPtr, pub db: Rc<RefCell<Option<rusqlite::Connection>>>, pub statements: Rc<RefCell<Vec<InnerStatementPtr>>>, pub ignore_next_sqlite_error: Rc<Cell<bool>>, pub return_arrays: Cell<bool>, pub use_big_ints: Cell<bool>, pub allow_bare_named_params: Cell<bool>, pub allow_unknown_named_params: Cell<bool>, pub is_iter_finished: Cell<bool>, pub iter_generation: Cell<u64>, /// Tracks all allocated `IteratorContext` pointers so they can be freed /// when the statement is dropped (handles abandoned iterators that never /// call `return()`). pub(crate) iter_contexts: RefCell<Vec<*mut IteratorContext>>, } pub(crate) struct IteratorContext { statement: *const StatementSync, statement_ref: v8::Global<v8::Value>, expected_generation: u64, finished: Cell<bool>, finalized_functions: Cell<u8>, next_func: RefCell<Option<v8::Weak<v8::Function>>>, return_func: RefCell<Option<v8::Weak<v8::Function>>>, } impl Drop for StatementSync { fn drop(&mut self) { // Free all tracked IteratorContext allocations to prevent memory leaks // from abandoned iterators that never called `return()`. for ctx_ptr in self.iter_contexts.borrow().iter() { // SAFETY: Each pointer was allocated via `Box::into_raw` in `iterate()` // and has not been freed yet (freed contexts are removed from the vec). unsafe { drop(Box::from_raw(*ctx_ptr)); } } let mut statements = self.statements.borrow_mut(); let mut finalized_stmt = None; if let Some(pos) = statements .iter() .position(|stmt| Rc::ptr_eq(stmt, &self.inner)) { let stmt = statements.remove(pos); finalized_stmt = stmt.get(); stmt.set(None); } if let Some(ptr) = finalized_stmt { // SAFETY: `ptr` is a valid pointer to a sqlite3_stmt. unsafe { ffi::sqlite3_finalize(ptr); } } } } fn release_iterator_context(ctx_ptr: *mut IteratorContext) { // SAFETY: `ctx_ptr` was allocated with `Box::into_raw` in `iterate()` and // remains valid until both callback functions have been finalized. let ctx = unsafe { &*ctx_ptr }; let finalized_functions = ctx.finalized_functions.get() + 1; ctx.finalized_functions.set(finalized_functions); if finalized_functions != 2 { return; } // SAFETY: `ctx.statement_ref` keeps the JS wrapper, and therefore this // cppgc object, alive until the context is dropped below. let statement = unsafe { &*ctx.statement }; statement .iter_contexts .borrow_mut() .retain(|ptr| *ptr != ctx_ptr); // SAFETY: Both callback functions are gone, so no `v8::External` can reach // this context again. unsafe { drop(Box::from_raw(ctx_ptr)); } } pub(crate) fn check_error_code( r: i32, db: *mut ffi::sqlite3, ) -> Result<(), SqliteError> { if r != ffi::SQLITE_OK { // SAFETY: lifetime of the connection is guaranteed by reference // counting. let err_message = unsafe { ffi::sqlite3_errmsg(db) }; if !err_message.is_null() { // SAFETY: `err_msg` is a valid pointer to a null-terminated string. let err_message = unsafe { std::ffi::CStr::from_ptr(err_message) } .to_string_lossy() .into_owned(); // SAFETY: `err_str` is a valid pointer to a null-terminated string. let err_str = unsafe { std::ffi::CStr::from_ptr(ffi::sqlite3_errstr(r)) } .to_string_lossy() .into_owned(); return Err(SqliteError::SqliteSysError { message: err_message, errcode: r as _, errstr: err_str, }); } } Ok(()) } pub(crate) fn check_error_code2(r: i32) -> Result<(), SqliteError> { // SAFETY: `ffi::sqlite3_errstr` is a valid function that returns a pointer to a null-terminated // string. let err_str = unsafe { std::ffi::CStr::from_ptr(ffi::sqlite3_errstr(r)) } .to_string_lossy() .into_owned(); Err(SqliteError::SqliteSysError { message: err_str.clone(), errcode: r as _, errstr: err_str, }) } // SAFETY: we're sure this can be GCed unsafe impl GarbageCollected for StatementSync { fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {} fn get_name(&self) -> &'static std::ffi::CStr { c"StatementSync" } } impl StatementExecution for StatementSync { fn stmt_ptr(&self) -> Result<*mut ffi::sqlite3_stmt, SqliteError> { let ptr = self.inner.get(); match ptr { Some(p) => Ok(p), None => Err(SqliteError::StatementFinalized), } } fn step(&self) -> Result<bool, SqliteError> { let raw = self.stmt_ptr()?; // SAFETY: `raw` 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 { return Ok(true); } if r != ffi::SQLITE_ROW { self.check_error_code_impl(r)?; } } Ok(false) } fn return_arrays(&self) -> bool { self.return_arrays.get() } fn use_big_ints(&self) -> bool { self.use_big_ints.get() } fn check_bind_result(&self, r: i32) -> Result<(), SqliteError> { self.check_error_code_impl(r) } } impl StatementSync { fn assert_statement_finalized(&self) -> Result<(), SqliteError> { if self.inner.get().is_none() { return Err(SqliteError::StatementFinalized); } Ok(()) } // Clear the prepared statement back to its initial state. fn reset(&self) -> Result<(), SqliteError> { let raw = self.stmt_ptr()?; // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the StatementSync instance. let r = unsafe { ffi::sqlite3_reset(raw) }; self.check_error_code_impl(r) } fn invalidate_iter(&self) { self .iter_generation .set(self.iter_generation.get().wrapping_add(1)); } fn check_error_code_impl(&self, r: i32) -> Result<(), SqliteError> { if r != ffi::SQLITE_OK { if self.ignore_next_sqlite_error.get() { self.ignore_next_sqlite_error.set(false); return Ok(()); } let db = self.db.borrow(); let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?; // SAFETY: db.handle() is valid unsafe { check_error_code(r, db.handle())?; } } Ok(()) } // Bind the parameters to the prepared statement. fn bind_params( &self, scope: &mut v8::PinScope<'_, '_>, params: Option<&v8::FunctionCallbackArguments>, ) -> Result<(), SqliteError> { let raw = self.stmt_ptr()?; // Reset the prepared statement to its initial state. // SAFETY: `raw` is a valid pointer to a sqlite3_stmt unsafe { let r = ffi::sqlite3_clear_bindings(raw); self.check_error_code_impl(r)?; } let mut anon_start = 0; if let Some(params) = params { let param0 = params.get(0); if param0.is_object() && !param0.is_array_buffer_view() { let obj = v8::Local::<v8::Object>::try_from(param0).unwrap(); let keys = obj .get_property_names(scope, GetPropertyNamesArgs::default()) .ok_or(validators::Error::V8Exception)?; // Allow specifying named parameters without the SQLite prefix character to improve // ergonomics. This can be disabled with `StatementSync#setAllowBareNamedParams`. let mut bare_named_params = std::collections::HashMap::new(); if self.allow_bare_named_params.get() { // SAFETY: `raw` is a valid pointer to a sqlite3_stmt. let param_count = unsafe { ffi::sqlite3_bind_parameter_count(raw) }; for i in 1..=param_count { // SAFETY: `raw` is a valid pointer to a sqlite3_stmt. let full_name = unsafe { let name = ffi::sqlite3_bind_parameter_name(raw, i); if name.is_null() { continue; } std::ffi::CStr::from_ptr(name).to_bytes() }; let bare_name = &full_name[1..]; let e = bare_named_params.insert(bare_name, i); if let Some(existing_index) = e { let bare_name_str = std::str::from_utf8(bare_name)?; let full_name_str = std::str::from_utf8(full_name)?; // SAFETY: `raw` is a valid pointer to a sqlite3_stmt. unsafe { let existing_full_name = ffi::sqlite3_bind_parameter_name(raw, existing_index); let existing_full_name_str = std::ffi::CStr::from_ptr(existing_full_name).to_str()?; return Err(SqliteError::DuplicateNamedParameter( bare_name_str.to_string(), existing_full_name_str.to_string(), full_name_str.to_string(), )); } } } } let len = keys.length(); for j in 0..len { let key = keys .get_index(scope, j) .ok_or(validators::Error::V8Exception)?; let key_str = key.to_rust_string_lossy(scope); let key_c = std::ffi::CString::new(key_str.as_bytes())?; // SAFETY: `raw` is a valid pointer to a sqlite3_stmt. let mut r = unsafe { ffi::sqlite3_bind_parameter_index(raw, key_c.as_ptr() as *const _) }; if r == 0 { let lookup = bare_named_params.get(key_c.as_bytes()); if let Some(index) = lookup { r = *index; } if r == 0 { if self.allow_unknown_named_params.get() { continue; } return Err(SqliteError::UnknownNamedParameter(key_str)); } } let value = obj.get(scope, key).ok_or(validators::Error::V8Exception)?; self.bind_value(scope, value, r)?; } anon_start += 1; } // SAFETY: `raw` is a valid pointer to a sqlite3_stmt. let sql_param_count = unsafe { ffi::sqlite3_bind_parameter_count(raw) }; let mut positional_idx = 1; for i in anon_start..params.length() { // Find the next positional parameter slot. // Skip named parameters (:name, $name, @name) but include anonymous (?) // and numbered (?NNN) parameters. while positional_idx <= sql_param_count { // SAFETY: `raw` is a valid pointer to a sqlite3_stmt. let name_ptr = unsafe { ffi::sqlite3_bind_parameter_name(raw, positional_idx) }; if name_ptr.is_null() // SAFETY: short-circuiting guarantees name_ptr is non-null here || unsafe { *name_ptr as u8 == b'?' } { break; } // Named parameter (:name, $name, @name) - skip it positional_idx += 1; } let value = params.get(i); self.bind_value(scope, value, positional_idx)?; positional_idx += 1; } } Ok(()) } } struct ResetGuard<'a>(&'a StatementSync); impl Drop for ResetGuard<'_> { fn drop(&mut self) { let _ = self.0.reset(); } } // 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] #[cppgc] fn new(_: bool) -> Result<StatementSync, SqliteError> { 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. #[reentrant] fn get<'a>( &self, scope: &mut v8::PinScope<'a, '_>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result<v8::Local<'a, v8::Value>, SqliteError> { self.invalidate_iter(); self.reset()?; self.bind_params(scope, params)?; let _reset = ResetGuard(self); let entry = self.read_row(scope)?; let result = entry.unwrap_or_else(|| v8::undefined(scope).into()); Ok(result) } // Executes a prepared statement and returns an object summarizing the resulting // changes. // // Optionally, parameters can be bound to the prepared statement. #[reentrant] fn run( &self, scope: &mut v8::PinScope<'_, '_>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result<RunStatementResult, SqliteError> { self.invalidate_iter(); let db = self.db.borrow(); let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?; self.bind_params(scope, params)?; let reset = ResetGuard(self); self.step()?; // Reset to return correct change metadata. drop(reset); Ok(RunStatementResult { last_insert_rowid: db.last_insert_rowid(), changes: db.changes(), use_big_ints: self.use_big_ints.get(), }) } // 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. #[reentrant] fn all<'a>( &self, scope: &mut v8::PinScope<'a, '_>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result<v8::Local<'a, v8::Array>, SqliteError> { self.invalidate_iter(); self.bind_params(scope, params)?; let _reset = ResetGuard(self); // Column names are constant across every row of the result set. Build the // V8 name strings once here and reuse them for all rows, instead of // re-creating them per row (as the generic `read_row` does): this drops a // `sqlite3_column_name` FFI call and a string allocation per column per // row, and gives every row object the same shape (hidden class). let num_cols = self.column_count()?; let return_arrays = self.return_arrays(); let mut names = Vec::with_capacity(num_cols as usize); if !return_arrays { for i in 0..num_cols { let name = self.column_name(i)?; names.push( v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal) .unwrap() .into(), ); } } let null = v8::null(scope).into(); let mut arr = vec![]; while !self.step()? { let mut values = Vec::with_capacity(num_cols as usize); for i in 0..num_cols { values.push(self.column_value(i, scope)?); } let row = if return_arrays { v8::Array::new_with_elements(scope, &values).into() } else { v8::Object::with_prototype_and_properties(scope, null, &names, &values) .into() }; arr.push(row); } let arr = v8::Array::new_with_elements(scope, &arr); Ok(arr) } #[reentrant] fn iterate<'a>( &self, scope: &mut v8::PinScope<'a, '_>, #[varargs] params: Option<&v8::FunctionCallbackArguments>, ) -> Result<v8::Local<'a, v8::Object>, SqliteError> { macro_rules! v8_static_strings { ($($ident:ident = $str:literal),* $(,)?) => { $( pub static $ident: deno_core::FastStaticString = deno_core::ascii_str!($str); )* }; } v8_static_strings! { ITERATOR = "Iterator", PROTOTYPE = "prototype", NEXT = "next", RETURN = "return", DONE = "done", VALUE = "value", __STATEMENT_REF = "__statement_ref", } self.invalidate_iter(); self.reset()?; self.bind_params(scope, params)?; let args = params.expect("StatementSync#iterate expected callback args"); let statement_ref: v8::Local<v8::Value> = args.this().into(); // Capture the current generation so the iterator can detect invalidation. // Store a strong handle to the JS wrapper in the callback data itself so // detached `next`/`return` functions keep the native statement alive. let iter_ctx = Box::into_raw(Box::new(IteratorContext { statement: self as *const StatementSync, statement_ref: v8::Global::new(scope, statement_ref), expected_generation: self.iter_generation.get(), finished: Cell::new(false), finalized_functions: Cell::new(0), next_func: RefCell::new(None), return_func: RefCell::new(None), })); // Track the allocation so it can be freed on Drop if the isolate tears down // before the weak function finalizers run. self.iter_contexts.borrow_mut().push(iter_ctx); let iterate_next = |scope: &mut v8::PinScope<'_, '_>, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue| { let external = v8::Local::<v8::External>::try_from(args.data()) .expect("Iterator#next expected external data"); // SAFETY: `external` wraps a pointer allocated via `Box::into_raw` in // `iterate()`. It remains valid while this callback function is live // because the context is released by weak finalizers on the generated // `next` and `return` functions. let ctx = unsafe { &*(external.value() as *const IteratorContext) }; // SAFETY: The context holds a strong reference to the statement's JS // wrapper, which prevents the StatementSync cppgc object from being // garbage collected while detached callbacks are alive. let statement = unsafe { &*ctx.statement }; let names = &[ DONE.v8_string(scope).unwrap().into(), VALUE.v8_string(scope).unwrap().into(), ]; // If this iterator already finished, return done regardless of // whether the statement was subsequently reset. if ctx.finished.get() { let values = &[v8::Boolean::new(scope, true).into(), v8::null(scope).into()]; let null = v8::null(scope).into(); let result = v8::Object::with_prototype_and_properties(scope, null, names, values); rv.set(result.into()); return; } // Check if iterator was invalidated by get/all/run/iterate if statement.iter_generation.get() != ctx.expected_generation { let msg = v8::String::new(scope, "This iterator was invalidated because the statement was reset by calling get(), all(), run(), or iterate() on the same statement object.") .unwrap(); let err = v8::Exception::error(scope, msg); let code_key = v8::String::new(scope, "code").unwrap(); let code_val = v8::String::new(scope, "ERR_INVALID_STATE").unwrap(); err.to_object(scope).unwrap().set( scope, code_key.into(), code_val.into(), ); scope.throw_exception(err); return; } let Ok(Some(row)) = statement.read_row(scope) else { let _ = statement.reset(); statement.is_iter_finished.set(true); ctx.finished.set(true); let values = &[v8::Boolean::new(scope, true).into(), v8::null(scope).into()]; let null = v8::null(scope).into(); let result = v8::Object::with_prototype_and_properties(scope, null, names, values); rv.set(result.into()); return; }; let values = &[v8::Boolean::new(scope, false).into(), row]; let null = v8::null(scope).into(); let result = v8::Object::with_prototype_and_properties(scope, null, names, values); rv.set(result.into()); }; let iterate_return = |scope: &mut v8::PinScope<'_, '_>, args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue| { let external = v8::Local::<v8::External>::try_from(args.data()) .expect("Iterator#return expected external data"); let ctx_ptr = external.value() as *const IteratorContext; // SAFETY: `ctx_ptr` was allocated via `Box::into_raw` in `iterate()` // and remains valid while this callback function is live because the // generated functions' weak finalizers release it. let ctx = unsafe { &*ctx_ptr }; // SAFETY: The statement pointer is kept alive by `ctx.statement_ref`. let statement = unsafe { &*ctx.statement }; // Note: We do NOT free the IteratorContext here because `next()` may // still be called after `return()` (e.g., the JS protocol allows it). // It will be freed after both generated callback functions are collected. ctx.finished.set(true); statement.is_iter_finished.set(true); let _ = statement.reset(); let names = &[ DONE.v8_string(scope).unwrap().into(), VALUE.v8_string(scope).unwrap().into(), ]; let values = &[v8::Boolean::new(scope, true).into(), v8::null(scope).into()]; let null = v8::null(scope).into(); let result = v8::Object::with_prototype_and_properties(scope, null, names, values); rv.set(result.into()); }; let external = v8::External::new(scope, iter_ctx as _); let next_func = v8::Function::builder(iterate_next) .data(external.into()) .build(scope) .expect("Failed to create Iterator#next function"); let return_func = v8::Function::builder(iterate_return) .data(external.into()) .build(scope) .expect("Failed to create Iterator#return function"); let weak_next = v8::Weak::with_finalizer( scope, next_func, Box::new(move |_| release_iterator_context(iter_ctx)), ); let weak_return = v8::Weak::with_finalizer( scope, return_func, Box::new(move |_| release_iterator_context(iter_ctx)), ); // SAFETY: `iter_ctx` was allocated above and is kept alive by the weak // handles stored inside it. unsafe { *(*iter_ctx).next_func.borrow_mut() = Some(weak_next); *(*iter_ctx).return_func.borrow_mut() = Some(weak_return); } let global = scope.get_current_context().global(scope); let iter_str = ITERATOR.v8_string(scope).unwrap(); let js_iterator = global .get(scope, iter_str.into()) .ok_or(validators::Error::V8Exception)?; let js_iterator = v8::Local::<v8::Object>::try_from(js_iterator).map_err(|_| { validators::Error::InvalidArgType("Iterator must be an object.".into()) })?; let proto_str = PROTOTYPE.v8_string(scope).unwrap(); let js_iterator_proto = js_iterator .get(scope, proto_str.into()) .ok_or(validators::Error::V8Exception)?; if !js_iterator_proto.is_object() && !js_iterator_proto.is_null() { return Err( validators::Error::InvalidArgType( "Iterator.prototype must be an object or null.".into(), ) .into(), ); } let names = &[ NEXT.v8_string(scope).unwrap().into(), RETURN.v8_string(scope).unwrap().into(), ]; let values = &[next_func.into(), return_func.into()]; let iterator = v8::Object::with_prototype_and_properties( scope, js_iterator_proto, names, values, ); // SAFETY: `iter_ctx` was allocated above and remains alive at least until // both generated callback functions are finalized. let statement_ref = v8::Local::new(scope, unsafe { &(*iter_ctx).statement_ref }); let attrs = v8::PropertyAttribute::READ_ONLY | v8::PropertyAttribute::DONT_ENUM | v8::PropertyAttribute::DONT_DELETE; let statement_ref_key = __STATEMENT_REF.v8_string(scope).unwrap().into(); iterator .define_own_property(scope, statement_ref_key, statement_ref, attrs) .unwrap(); self.is_iter_finished.set(false); Ok(iterator) } #[fast] #[undefined] fn set_allow_bare_named_parameters( &self, #[validate(validators::allow_bare_named_params_bool)] enabled: bool, ) -> Result<(), SqliteError> { self.assert_statement_finalized()?; self.allow_bare_named_params.set(enabled); Ok(()) } #[fast] #[undefined] fn set_allow_unknown_named_parameters( &self, #[validate(validators::allow_unknown_named_params_bool)] enabled: bool, ) -> Result<(), SqliteError> { self.assert_statement_finalized()?; self.allow_unknown_named_params.set(enabled); Ok(()) } #[fast] #[undefined] fn set_read_big_ints( &self, #[validate(validators::read_big_ints_bool)] enabled: bool, ) -> Result<(), SqliteError> { self.assert_statement_finalized()?; self.use_big_ints.set(enabled); Ok(()) } #[fast] #[undefined] fn set_return_arrays( &self, #[validate(validators::return_arrays_bool)] enabled: bool, ) -> Result<(), SqliteError> { self.assert_statement_finalized()?; self.return_arrays.set(enabled); Ok(()) } #[getter] #[rename("sourceSQL")] #[string] fn source_sql(&self) -> Result<String, SqliteError> { let inner = self.stmt_ptr()?; // SAFETY: `raw` is a valid pointer to a sqlite3_stmt // as it lives as long as the StatementSync instance. let source_sql = unsafe { let raw = ffi::sqlite3_sql(inner); std::ffi::CStr::from_ptr(raw as _) .to_string_lossy() .into_owned() }; Ok(source_sql) } #[getter] #[rename("expandedSQL")] #[string] fn expanded_sql(&self) -> Result<String, SqliteError> { let inner = self.stmt_ptr()?; // SAFETY: `inner` is a valid pointer to a sqlite3_stmt // as it lives as long as the StatementSync instance. unsafe { let raw = ffi::sqlite3_expanded_sql(inner); if raw.is_null() { return Err(SqliteError::InvalidExpandedSql); } let sql = std::ffi::CStr::from_ptr(raw as _) .to_string_lossy() .into_owned(); ffi::sqlite3_free(raw as _); Ok(sql) } } fn columns<'a>( &self, scope: &mut v8::PinScope<'a, '_>, ) -> Result<v8::Local<'a, v8::Array>, SqliteError> { v8_static_strings! { NAME = "name", COLUMN = "column", TABLE = "table", DATABASE = "database", TYPE = "type", } let column_count = self.column_count()?; let mut columns = Vec::with_capacity(column_count as usize); // Pre-create property keys let name_key = NAME.v8_string(scope).unwrap().into(); let column_key = COLUMN.v8_string(scope).unwrap().into(); let table_key = TABLE.v8_string(scope).unwrap().into(); let database_key = DATABASE.v8_string(scope).unwrap().into(); let type_key = TYPE.v8_string(scope).unwrap().into(); let keys = &[name_key, column_key, table_key, database_key, type_key]; let raw = self.stmt_ptr()?; for i in 0..column_count { // name: The name of the column in the result set // SAFETY: `raw` is a valid pointer to a sqlite3_stmt let name = unsafe { let name_ptr = ffi::sqlite3_column_name(raw, i); if !name_ptr.is_null() { let name_cstr = std::ffi::CStr::from_ptr(name_ptr as _); v8::String::new_from_utf8( scope, name_cstr.to_bytes(), v8::NewStringType::Normal, ) .unwrap() .into() } else { v8::null(scope).into() } }; // column: The unaliased name of the column in the origin table // SAFETY: `raw` is a valid pointer to a sqlite3_stmt let column = unsafe { let column_ptr = ffi::sqlite3_column_origin_name(raw, i); if !column_ptr.is_null() { let column_cstr = std::ffi::CStr::from_ptr(column_ptr as _); v8::String::new_from_utf8( scope, column_cstr.to_bytes(), v8::NewStringType::Normal, ) .unwrap() .into() } else { v8::null(scope).into() } }; // table: The unaliased name of the origin table // SAFETY: `raw` is a valid pointer to a sqlite3_stmt let table = unsafe { let table_ptr = ffi::sqlite3_column_table_name(raw, i); if !table_ptr.is_null() { let table_cstr = std::ffi::CStr::from_ptr(table_ptr as _); v8::String::new_from_utf8( scope, table_cstr.to_bytes(), v8::NewStringType::Normal, ) .unwrap() .into() } else { v8::null(scope).into() } }; // database: The unaliased name of the origin database // SAFETY: `raw` is a valid pointer to a sqlite3_stmt let database = unsafe { let database_ptr = ffi::sqlite3_column_database_name(raw, i); if !database_ptr.is_null() { let database_cstr = std::ffi::CStr::from_ptr(database_ptr as _); v8::String::new_from_utf8( scope, database_cstr.to_bytes(), v8::NewStringType::Normal, ) .unwrap() .into() } else { v8::null(scope).into() } }; // type: The declared data type of the column // SAFETY: `raw` is a valid pointer to a sqlite3_stmt let col_type = unsafe { let type_ptr = ffi::sqlite3_column_decltype(raw, i); if !type_ptr.is_null() { let type_cstr = std::ffi::CStr::from_ptr(type_ptr as _); v8::String::new_from_utf8( scope, type_cstr.to_bytes(), v8::NewStringType::Normal, ) .unwrap() .into() } else { v8::null(scope).into() } }; let values = &[name, column, table, database, col_type]; let null = v8::null(scope).into(); let obj = v8::Object::with_prototype_and_properties(scope, null, keys, values); columns.push(obj.into()); } Ok(v8::Array::new_with_elements(scope, &columns)) } }