// 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, 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() }; obj .set(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.set(scope, changes_str.into(), changes).unwrap(); Ok(obj.into()) } } pub type InnerStatementPtr = Rc>>; pub trait StatementExecution { fn stmt_ptr(&self) -> Result<*mut ffi::sqlite3_stmt, SqliteError>; fn step(&self) -> Result; 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 { 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, 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, 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 = 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 = 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>, 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>>, pub statements: Rc>>, pub ignore_next_sqlite_error: Rc>, pub return_arrays: Cell, pub use_big_ints: Cell, pub allow_bare_named_params: Cell, pub allow_unknown_named_params: Cell, pub is_iter_finished: Cell, pub iter_generation: Cell, /// 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>, } pub(crate) struct IteratorContext { statement: *const StatementSync, expected_generation: u64, finished: Cell, } 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); } } } } 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 { 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::::try_from(param0).unwrap(); let keys = obj .get_property_names(scope, GetPropertyNamesArgs::default()) .unwrap(); // 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).unwrap(); let key_str = key.to_rust_string_lossy(scope); let key_c = std::ffi::CString::new(key_str).unwrap(); // 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_c.into_string().unwrap(), )); } } let value = obj.get(scope, key).unwrap(); 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 { 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, 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 { 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, SqliteError> { self.invalidate_iter(); let mut arr = vec![]; self.bind_params(scope, params)?; let _reset = ResetGuard(self); while let Some(result) = self.read_row(scope)? { arr.push(result); } 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, 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)?; // Capture the current generation so the iterator can detect invalidation let iter_ctx = Box::into_raw(Box::new(IteratorContext { statement: self as *const StatementSync, expected_generation: self.iter_generation.get(), finished: Cell::new(false), })); // Track the allocation so it can be freed on Drop if the iterator is // abandoned (i.e. `return()` is never called). 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::::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 because it is either freed in // `iterate_return` (after which `next()` won't be called) or in // `StatementSync::drop`. let ctx = unsafe { &*(external.value() as *const IteratorContext) }; // SAFETY: The iterator object holds a reference to the statement's JS // wrapper via the `__statement_ref` property, which prevents the // StatementSync cppgc object from being garbage collected while the // iterator is 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::::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 the iterator object's `__statement_ref` property prevents the // StatementSync from being garbage collected while this iterator exists. let ctx = unsafe { &*ctx_ptr }; // SAFETY: The statement pointer is kept alive by `__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). // All IteratorContext allocations are freed in StatementSync::drop. 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 global = scope.get_current_context().global(scope); let iter_str = ITERATOR.v8_string(scope).unwrap(); let js_iterator: v8::Local = { global .get(scope, iter_str.into()) .unwrap() .try_into() .unwrap() }; let proto_str = PROTOTYPE.v8_string(scope).unwrap(); let js_iterator_proto = js_iterator.get(scope, proto_str.into()).unwrap(); let names = &[ NEXT.v8_string(scope).unwrap().into(), RETURN.v8_string(scope).unwrap().into(), __STATEMENT_REF.v8_string(scope).unwrap().into(), ]; // Get the cppgc wrapper object to keep the statement alive // We store a reference to the statement object on the iterator to prevent // the GC from collecting it while the iterator is still in use. let statement_ref = if let Some(args) = params { args.this().into() } else { v8::undefined(scope).into() }; let values = &[next_func.into(), return_func.into(), statement_ref]; let iterator = v8::Object::with_prototype_and_properties( scope, js_iterator_proto, names, values, ); 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 { 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 { 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, 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)) } }