use std::borrow::Cow;
use std::cell::Cell;
use std::cell::RefCell;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::c_char;
use std::ffi::c_void;
use std::path::Path;
use std::ptr::NonNull;
use std::ptr::null;
use std::rc::Rc;
use deno_core::FromV8;
use deno_core::GarbageCollected;
use deno_core::OpState;
use deno_core::convert::OptionUndefined;
use deno_core::cppgc;
use deno_core::op2;
use deno_core::v8;
use deno_core::v8_static_strings;
use deno_permissions::OpenAccessKind;
use deno_permissions::PermissionsContainer;
use rusqlite::ffi as libsqlite3_sys;
use rusqlite::ffi::SQLITE_DBCONFIG_DEFENSIVE;
use rusqlite::ffi::SQLITE_DBCONFIG_DQS_DDL;
use rusqlite::ffi::SQLITE_DBCONFIG_DQS_DML;
use rusqlite::ffi::sqlite3_create_window_function;
use rusqlite::ffi::sqlite3_db_filename;
use rusqlite::limits::Limit;
use super::Session;
use super::SqliteError;
use super::StatementSync;
use super::session::SessionOptions;
use super::sql_tag_store::SQLTagStore;
use super::statement::InnerStatementPtr;
use super::statement::check_error_code;
use super::statement::check_error_code2;
use super::validators;
const SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: i32 = 1005;
const SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE: i32 = 1021;
const MAX_SAFE_JS_INTEGER: i64 = 9_007_199_254_740_991;
struct DatabaseSyncOptions {
open: bool,
enable_foreign_key_constraints: bool,
read_only: bool,
allow_extension: bool,
enable_double_quoted_string_literals: bool,
use_big_int_arguments: bool,
allow_bare_named_params: bool,
return_arrays: bool,
allow_unknown_named_params: bool,
is_defensive_mode: bool,
timeout: u64,
}
impl<'a> FromV8<'a> for DatabaseSyncOptions {
type Error = validators::Error;
fn from_v8(
scope: &mut v8::PinScope<'a, '_>,
value: v8::Local<'a, v8::Value>,
) -> Result<Self, Self::Error> {
use validators::Error;
if value.is_undefined() {
return Ok(Self::default());
}
let Ok(obj) = v8::Local::<v8::Object>::try_from(value) else {
return Err(Error::InvalidArgType(
"The \"options\" argument must be an object.",
));
};
let mut options = Self::default();
v8_static_strings! {
OPEN_STRING = "open",
ENABLE_FOREIGN_KEY_CONSTRAINTS_STRING = "enableForeignKeyConstraints",
READ_ONLY_STRING = "readOnly",
ALLOW_EXTENSION_STRING = "allowExtension",
ENABLE_DOUBLE_QUOTED_STRING_LITERALS_STRING = "enableDoubleQuotedStringLiterals",
TIMEOUT_STRING = "timeout",
READ_BIG_INTS = "readBigInts",
RETURN_ARRAYS = "returnArrays",
ALLOW_BARE_NAMED_PARAMS = "allowBareNamedParameters",
ALLOW_UNKNOWN_NAMED_PARAMS = "allowUnknownNamedParameters",
DEFENSIVE_STRING = "defensive",
}
let open_string = OPEN_STRING.v8_string(scope).unwrap();
if let Some(open) = obj.get(scope, open_string.into())
&& !open.is_undefined()
{
options.open = v8::Local::<v8::Boolean>::try_from(open)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.open\" argument must be a boolean.",
)
})?
.is_true();
}
let read_only_string = READ_ONLY_STRING.v8_string(scope).unwrap();
if let Some(read_only) = obj.get(scope, read_only_string.into())
&& !read_only.is_undefined()
{
options.read_only = v8::Local::<v8::Boolean>::try_from(read_only)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.readOnly\" argument must be a boolean.",
)
})?
.is_true();
}
let enable_foreign_key_constraints_string =
ENABLE_FOREIGN_KEY_CONSTRAINTS_STRING
.v8_string(scope)
.unwrap();
if let Some(enable_foreign_key_constraints) =
obj.get(scope, enable_foreign_key_constraints_string.into())
&& !enable_foreign_key_constraints.is_undefined()
{
options.enable_foreign_key_constraints =
v8::Local::<v8::Boolean>::try_from(enable_foreign_key_constraints)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.enableForeignKeyConstraints\" argument must be a boolean.",
)
})?
.is_true();
}
let allow_extension_string =
ALLOW_EXTENSION_STRING.v8_string(scope).unwrap();
if let Some(allow_extension) = obj.get(scope, allow_extension_string.into())
&& !allow_extension.is_undefined()
{
options.allow_extension =
v8::Local::<v8::Boolean>::try_from(allow_extension)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.allowExtension\" argument must be a boolean.",
)
})?
.is_true();
}
let enable_double_quoted_string_literals_string =
ENABLE_DOUBLE_QUOTED_STRING_LITERALS_STRING
.v8_string(scope)
.unwrap();
if let Some(enable_double_quoted_string_literals) =
obj.get(scope, enable_double_quoted_string_literals_string.into())
&& !enable_double_quoted_string_literals.is_undefined()
{
options.enable_double_quoted_string_literals =
v8::Local::<v8::Boolean>::try_from(enable_double_quoted_string_literals)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.enableDoubleQuotedStringLiterals\" argument must be a boolean.",
)
})?
.is_true();
}
let timeout_string = TIMEOUT_STRING.v8_string(scope).unwrap();
if let Some(timeout) = obj.get(scope, timeout_string.into())
&& !timeout.is_undefined()
{
let timeout = v8::Local::<v8::Integer>::try_from(timeout)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.timeout\" argument must be an integer.",
)
})?
.value();
if timeout > 0 {
options.timeout = timeout as u64;
}
}
let read_big_ints_string = READ_BIG_INTS.v8_string(scope).unwrap();
if let Some(read_big_ints) = obj.get(scope, read_big_ints_string.into())
&& !read_big_ints.is_undefined()
{
options.use_big_int_arguments =
v8::Local::<v8::Boolean>::try_from(read_big_ints)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.readBigInts\" argument must be a boolean.",
)
})?
.is_true();
}
let return_arrays_string = RETURN_ARRAYS.v8_string(scope).unwrap();
if let Some(return_arrays) = obj.get(scope, return_arrays_string.into())
&& !return_arrays.is_undefined()
{
options.return_arrays = v8::Local::<v8::Boolean>::try_from(return_arrays)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.returnArrays\" argument must be a boolean.",
)
})?
.is_true();
}
let allow_bare_named_params_string =
ALLOW_BARE_NAMED_PARAMS.v8_string(scope).unwrap();
if let Some(allow_bare_named_params) =
obj.get(scope, allow_bare_named_params_string.into())
&& !allow_bare_named_params.is_undefined()
{
options.allow_bare_named_params =
v8::Local::<v8::Boolean>::try_from(allow_bare_named_params)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.allowBareNamedParameters\" argument must be a boolean.",
)
})?
.is_true();
}
let allow_unknown_named_params_string =
ALLOW_UNKNOWN_NAMED_PARAMS.v8_string(scope).unwrap();
if let Some(allow_unknown_named_params) =
obj.get(scope, allow_unknown_named_params_string.into())
&& !allow_unknown_named_params.is_undefined()
{
options.allow_unknown_named_params =
v8::Local::<v8::Boolean>::try_from(allow_unknown_named_params)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.allowUnknownNamedParameters\" argument must be a boolean.",
)
})?
.is_true();
}
let defensive_string = DEFENSIVE_STRING.v8_string(scope).unwrap();
if let Some(is_defensive_mode) = obj.get(scope, defensive_string.into())
&& !is_defensive_mode.is_undefined()
{
options.is_defensive_mode =
v8::Local::<v8::Boolean>::try_from(is_defensive_mode)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.defensive\" argument must be a boolean.",
)
})?
.is_true();
}
Ok(options)
}
}
impl Default for DatabaseSyncOptions {
fn default() -> Self {
DatabaseSyncOptions {
open: true,
enable_foreign_key_constraints: true,
read_only: false,
allow_extension: false,
enable_double_quoted_string_literals: false,
use_big_int_arguments: false,
return_arrays: false,
allow_bare_named_params: true,
allow_unknown_named_params: false,
is_defensive_mode: false,
timeout: 0,
}
}
}
struct AggregateFunctionOption<'a> {
deterministic: bool,
direct_only: bool,
use_big_int_arguments: bool,
varargs: bool,
start: v8::Local<'a, v8::Value>,
step: v8::Local<'a, v8::Function>,
result: Option<v8::Local<'a, v8::Function>>,
inverse: Option<v8::Local<'a, v8::Function>>,
}
impl<'a> AggregateFunctionOption<'a> {
fn from_value(
scope: &mut v8::PinScope<'a, '_>,
value: v8::Local<'a, v8::Value>,
) -> Result<Self, validators::Error> {
use validators::Error;
if !value.is_object() {
return Err(Error::InvalidArgType(
"The \"options\" argument must be an object.",
));
}
v8_static_strings! {
DETERMINISTIC_STRING = "deterministic",
DIRECT_ONLY_STRING = "directOnly",
USE_BIG_INT_ARGUMENTS_STRING = "useBigIntArguments",
VARARGS_STRING = "varargs",
START_STRING = "start",
STEP_STRING = "step",
RESULT_STRING = "result",
INVERSE_STRING = "inverse",
}
let start_key = START_STRING.v8_string(scope).unwrap();
let start_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, start_key.into())
.unwrap();
if start_value.is_undefined() {
return Err(Error::InvalidArgType(
"The \"options.start\" argument must be a function or a primitive value.",
));
}
let step_key = STEP_STRING.v8_string(scope).unwrap();
let step_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, step_key.into())
.unwrap();
let step_function = v8::Local::<v8::Function>::try_from(step_value)
.map_err(|_| {
Error::InvalidArgType(
"The \"options.step\" argument must be a function.",
)
})?;
let result_key = RESULT_STRING.v8_string(scope).unwrap();
let result_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, result_key.into())
.unwrap();
let result_function = if result_value.is_undefined() {
None
} else {
let func =
v8::Local::<v8::Function>::try_from(result_value).map_err(|_| {
Error::InvalidArgType(
"The \"options.result\" argument must be a function.",
)
})?;
Some(func)
};
let mut deterministic = false;
let mut use_big_int_arguments = false;
let mut varargs = false;
let mut direct_only = false;
let deterministic_key = DETERMINISTIC_STRING.v8_string(scope).unwrap();
let deterministic_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, deterministic_key.into())
.unwrap();
if !deterministic_value.is_undefined() {
if !deterministic_value.is_boolean() {
return Err(Error::InvalidArgType(
"The \"options.deterministic\" argument must be a boolean.",
));
}
deterministic = deterministic_value.boolean_value(scope);
}
let use_bigint_key = USE_BIG_INT_ARGUMENTS_STRING.v8_string(scope).unwrap();
let bigint_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, use_bigint_key.into())
.unwrap();
if !bigint_value.is_undefined() {
if !bigint_value.is_boolean() {
return Err(Error::InvalidArgType(
"The \"options.useBigIntArguments\" argument must be a boolean.",
));
}
use_big_int_arguments = bigint_value.boolean_value(scope);
}
let varargs_key = VARARGS_STRING.v8_string(scope).unwrap();
let varargs_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, varargs_key.into())
.unwrap();
if !varargs_value.is_undefined() {
if !varargs_value.is_boolean() {
return Err(Error::InvalidArgType(
"The \"options.varargs\" argument must be a boolean.",
));
}
varargs = varargs_value.boolean_value(scope);
}
let direct_only_key = DIRECT_ONLY_STRING.v8_string(scope).unwrap();
let direct_only_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, direct_only_key.into())
.unwrap();
if !direct_only_value.is_undefined() {
if !direct_only_value.is_boolean() {
return Err(Error::InvalidArgType(
"The \"options.directOnly\" argument must be a boolean.",
));
}
direct_only = direct_only_value.boolean_value(scope);
}
let inverse_key = INVERSE_STRING.v8_string(scope).unwrap();
let inverse_value = v8::Local::<v8::Object>::try_from(value)
.unwrap()
.get(scope, inverse_key.into())
.unwrap();
let inverse_function = if inverse_value.is_undefined() {
None
} else {
let func =
v8::Local::<v8::Function>::try_from(inverse_value).map_err(|_| {
Error::InvalidArgType(
"The \"options.inverse\" argument must be a function.",
)
})?;
Some(func)
};
Ok(AggregateFunctionOption {
deterministic,
direct_only,
use_big_int_arguments,
varargs,
start: start_value,
step: step_function,
result: result_function,
inverse: inverse_function,
})
}
}
struct ApplyChangesetOptions<'a> {
filter: Option<v8::Local<'a, v8::Value>>,
on_conflict: Option<v8::Local<'a, v8::Value>>,
}
impl<'a> ApplyChangesetOptions<'a> {
fn from_value(
scope: &mut v8::PinScope<'a, '_>,
value: v8::Local<'a, v8::Value>,
) -> Result<Option<Self>, validators::Error> {
use validators::Error;
if value.is_undefined() {
return Ok(None);
}
let obj = v8::Local::<v8::Object>::try_from(value).map_err(|_| {
Error::InvalidArgType("The \"options\" argument must be an object.")
})?;
let mut options = Self {
filter: None,
on_conflict: None,
};
v8_static_strings! {
FILTER_STRING = "filter",
ON_CONFLICT_STRING = "onConflict",
}
let filter_string = FILTER_STRING.v8_string(scope).unwrap();
if let Some(filter) = obj.get(scope, filter_string.into())
&& !filter.is_undefined()
{
if !filter.is_function() {
return Err(Error::InvalidArgType(
"The \"options.filter\" argument must be a function.",
));
}
options.filter = Some(filter);
}
let on_conflict_string = ON_CONFLICT_STRING.v8_string(scope).unwrap();
if let Some(on_conflict) = obj.get(scope, on_conflict_string.into())
&& !on_conflict.is_undefined()
{
if !on_conflict.is_function() {
return Err(Error::InvalidArgType(
"The \"options.onConflict\" argument must be a function.",
));
}
options.on_conflict = Some(on_conflict);
}
Ok(Some(options))
}
}
pub struct DatabaseSync {
pub conn: Rc<RefCell<Option<rusqlite::Connection>>>,
statements: Rc<RefCell<Vec<InnerStatementPtr>>>,
options: DatabaseSyncOptions,
location: String,
ignore_next_sqlite_error: Rc<Cell<bool>>,
authorizer_data: Rc<RefCell<Option<*mut AuthorizerData>>>,
}
unsafe impl GarbageCollected for DatabaseSync {
fn trace(&self, _visitor: &mut deno_core::v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
c"DatabaseSync"
}
}
fn set_db_config(
conn: &rusqlite::Connection,
config: i32,
value: bool,
) -> bool {
unsafe {
let mut set = 0;
let r = libsqlite3_sys::sqlite3_db_config(
conn.handle(),
config,
value as i32,
&mut set,
);
if r != libsqlite3_sys::SQLITE_OK {
panic!("Failed to set db config");
}
set == value as i32
}
}
fn open_db(
state: &mut OpState,
location: &str,
options: &DatabaseSyncOptions,
) -> Result<rusqlite::Connection, SqliteError> {
let perms = state.borrow::<PermissionsContainer>();
let disable_attach = perms
.check_has_all_permissions(Path::new(location))
.is_err();
if location == ":memory:" {
let conn = rusqlite::Connection::open_in_memory()?;
if disable_attach {
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE,
false
));
conn.set_limit(Limit::SQLITE_LIMIT_ATTACHED, 0)?;
}
if options.allow_extension {
perms.check_ffi_all()?;
} else {
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
false
));
}
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_DEFENSIVE,
options.is_defensive_mode,
));
return Ok(conn);
}
let location = perms
.check_open(
Cow::Borrowed(Path::new(location)),
match options.read_only {
true => OpenAccessKind::ReadNoFollow,
false => OpenAccessKind::ReadWriteNoFollow,
},
Some("node:sqlite"),
)?
.into_path();
if options.read_only {
let conn = rusqlite::Connection::open_with_flags(
location,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
)?;
if disable_attach {
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE,
false
));
conn.set_limit(Limit::SQLITE_LIMIT_ATTACHED, 0)?;
}
if options.allow_extension {
perms.check_ffi_all()?;
} else {
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
false
));
}
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_DEFENSIVE,
options.is_defensive_mode,
));
return Ok(conn);
}
let conn = rusqlite::Connection::open(location)?;
conn.busy_timeout(std::time::Duration::from_millis(options.timeout))?;
if options.allow_extension {
perms.check_ffi_all()?;
} else {
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION,
false
));
}
if disable_attach {
conn.set_limit(Limit::SQLITE_LIMIT_ATTACHED, 0)?;
}
assert!(set_db_config(
&conn,
SQLITE_DBCONFIG_DEFENSIVE,
options.is_defensive_mode,
));
Ok(conn)
}
fn is_open(
scope: &mut v8::PinScope<'_, '_>,
args: &v8::FunctionCallbackArguments,
) -> Result<(), SqliteError> {
let this_ = args.this();
let db = cppgc::try_unwrap_cppgc_object::<DatabaseSync>(scope, this_.into())
.ok_or(SqliteError::AlreadyClosed)?;
db.conn
.borrow()
.as_ref()
.ok_or(SqliteError::AlreadyClosed)?;
Ok(())
}
#[op2]
impl DatabaseSync {
#[constructor]
#[cppgc]
fn new(
state: &mut OpState,
#[string] location: String,
#[scoped] options: DatabaseSyncOptions,
) -> Result<DatabaseSync, SqliteError> {
let db = if options.open {
let db = open_db(state, &location, &options)?;
if options.enable_foreign_key_constraints {
db.execute("PRAGMA foreign_keys = ON", [])?;
} else {
db.execute("PRAGMA foreign_keys = OFF", [])?;
}
set_db_config(
&db,
SQLITE_DBCONFIG_DQS_DDL,
options.enable_double_quoted_string_literals,
);
set_db_config(
&db,
SQLITE_DBCONFIG_DQS_DML,
options.enable_double_quoted_string_literals,
);
Some(db)
} else {
None
};
Ok(DatabaseSync {
conn: Rc::new(RefCell::new(db)),
statements: Rc::new(RefCell::new(Vec::new())),
location,
options,
ignore_next_sqlite_error: Rc::new(Cell::new(false)),
authorizer_data: Rc::new(RefCell::new(None)),
})
}
#[fast]
#[undefined]
fn open(&self, state: &mut OpState) -> Result<(), SqliteError> {
if self.conn.borrow().is_some() {
return Err(SqliteError::AlreadyOpen);
}
let db = open_db(state, &self.location, &self.options)?;
if self.options.enable_foreign_key_constraints {
db.execute("PRAGMA foreign_keys = ON", [])?;
} else {
db.execute("PRAGMA foreign_keys = OFF", [])?;
}
set_db_config(
&db,
SQLITE_DBCONFIG_DQS_DDL,
self.options.enable_double_quoted_string_literals,
);
set_db_config(
&db,
SQLITE_DBCONFIG_DQS_DML,
self.options.enable_double_quoted_string_literals,
);
*self.conn.borrow_mut() = Some(db);
Ok(())
}
#[fast]
#[undefined]
fn close(&self) -> Result<(), SqliteError> {
if self.conn.borrow().is_none() {
return Err(SqliteError::AlreadyClosed);
}
for stmt in self.statements.borrow_mut().drain(..) {
match stmt.get() {
None => continue,
Some(ptr) => {
unsafe {
libsqlite3_sys::sqlite3_finalize(ptr);
};
stmt.set(None);
}
};
}
{
let mut authorizer_data = self.authorizer_data.borrow_mut();
if let Some(data_ptr) = authorizer_data.take() {
unsafe {
free_authorizer_data(data_ptr);
}
}
}
let _ = self.conn.borrow_mut().take();
Ok(())
}
#[fast]
#[validate(is_open)]
#[undefined]
fn exec(
&self,
#[validate(validators::sql_str)]
#[string]
sql: &str,
) -> Result<(), SqliteError> {
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::InUse)?;
if let Err(err) = db.execute_batch(sql) {
if self.consume_ignore_next_sqlite_error() {
return Ok(());
}
return Err(err.into());
}
Ok(())
}
#[validate(is_open)]
#[cppgc]
fn prepare(
&self,
#[validate(validators::sql_str)]
#[string]
sql: &str,
) -> Result<StatementSync, SqliteError> {
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 _,
sql.len() as i32,
&mut raw_stmt,
std::ptr::null_mut(),
)
};
check_error_code(r, raw_handle)?;
let stmt_cell = Rc::new(Cell::new(Some(raw_stmt)));
self.statements.borrow_mut().push(stmt_cell.clone());
Ok(StatementSync {
inner: stmt_cell,
db: self.conn.clone(),
statements: Rc::clone(&self.statements),
ignore_next_sqlite_error: Rc::clone(&self.ignore_next_sqlite_error),
return_arrays: Cell::new(self.options.return_arrays),
use_big_ints: Cell::new(self.options.use_big_int_arguments),
allow_bare_named_params: Cell::new(self.options.allow_bare_named_params),
allow_unknown_named_params: Cell::new(
self.options.allow_unknown_named_params,
),
is_iter_finished: Cell::new(false),
})
}
#[fast]
#[validate(is_open)]
#[undefined]
fn function<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
#[varargs] args: Option<&v8::FunctionCallbackArguments>,
) -> Result<(), SqliteError> {
let Some(args) = args.filter(|args| args.length() > 0) else {
return Err(
validators::Error::InvalidArgType(
"The \"name\" argument must be a string.",
)
.into(),
);
};
if !args.get(0).is_string() {
return Err(
validators::Error::InvalidArgType(
"The \"name\" argument must be a string.",
)
.into(),
);
}
let name = args.get(0).to_rust_string_lossy(scope);
let (options_value, function_value) = if args.length() < 3 {
(None, args.get(1))
} else {
(Some(args.get(1)), args.get(2))
};
let Ok(function) = v8::Local::<v8::Function>::try_from(function_value)
else {
return Err(
validators::Error::InvalidArgType(
"The \"function\" argument must be a function.",
)
.into(),
);
};
let mut use_big_int_arguments = false;
let mut varargs = false;
let mut deterministic = false;
let mut direct_only = false;
if let Some(value) = options_value
&& !value.is_undefined()
{
if value.is_null() || !value.is_object() {
return Err(
validators::Error::InvalidArgType(
"The \"options\" argument must be an object.",
)
.into(),
);
}
let options = v8::Local::<v8::Object>::try_from(value).unwrap();
v8_static_strings! {
USE_BIG_INT_ARGUMENTS = "useBigIntArguments",
VARARGS = "varargs",
DETERMINISTIC = "deterministic",
DIRECT_ONLY = "directOnly",
}
let use_bigint_key = USE_BIG_INT_ARGUMENTS.v8_string(scope).unwrap();
let bigint_value = options.get(scope, use_bigint_key.into()).unwrap();
if !bigint_value.is_undefined() {
if !bigint_value.is_boolean() {
return Err(
validators::Error::InvalidArgType(
"The \"options.useBigIntArguments\" argument must be a boolean.",
)
.into(),
);
}
use_big_int_arguments = bigint_value.boolean_value(scope);
}
let varargs_key = VARARGS.v8_string(scope).unwrap();
let varargs_value = options.get(scope, varargs_key.into()).unwrap();
if !varargs_value.is_undefined() {
if !varargs_value.is_boolean() {
return Err(
validators::Error::InvalidArgType(
"The \"options.varargs\" argument must be a boolean.",
)
.into(),
);
}
varargs = varargs_value.boolean_value(scope);
}
let deterministic_key = DETERMINISTIC.v8_string(scope).unwrap();
let deterministic_value =
options.get(scope, deterministic_key.into()).unwrap();
if !deterministic_value.is_undefined() {
if !deterministic_value.is_boolean() {
return Err(
validators::Error::InvalidArgType(
"The \"options.deterministic\" argument must be a boolean.",
)
.into(),
);
}
deterministic = deterministic_value.boolean_value(scope);
}
let direct_only_key = DIRECT_ONLY.v8_string(scope).unwrap();
let direct_only_value =
options.get(scope, direct_only_key.into()).unwrap();
if !direct_only_value.is_undefined() {
if !direct_only_value.is_boolean() {
return Err(
validators::Error::InvalidArgType(
"The \"options.directOnly\" argument must be a boolean.",
)
.into(),
);
}
direct_only = direct_only_value.boolean_value(scope);
}
}
v8_static_strings! {
LENGTH = "length",
}
let argc = if varargs {
-1
} else {
let length_key = LENGTH.v8_string(scope).unwrap();
let length = function.get(scope, length_key.into()).unwrap();
length.int32_value(scope).unwrap_or(0)
};
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::InUse)?;
let raw_handle = unsafe { db.handle() };
let name_cstring = CString::new(name)?;
let callback = v8::Global::new(scope, function).into_raw();
let context =
v8::Global::new(scope, scope.get_current_context()).into_raw();
let data = Box::new(CustomFunctionData {
callback,
context,
use_big_int_arguments,
ignore_next_sqlite_error: Rc::clone(&self.ignore_next_sqlite_error),
});
let data_ptr = Box::into_raw(data);
let mut text_rep = libsqlite3_sys::SQLITE_UTF8;
if deterministic {
text_rep |= libsqlite3_sys::SQLITE_DETERMINISTIC;
}
if direct_only {
text_rep |= libsqlite3_sys::SQLITE_DIRECTONLY;
}
let result = unsafe {
libsqlite3_sys::sqlite3_create_function_v2(
raw_handle,
name_cstring.as_ptr(),
argc,
text_rep,
data_ptr as *mut c_void,
Some(custom_function_handler),
None,
None,
Some(custom_function_destroy),
)
};
check_error_code(result, raw_handle)?;
Ok(())
}
#[fast]
#[reentrant]
fn apply_changeset<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
#[validate(validators::changeset_buffer)]
#[buffer]
changeset: &[u8],
options: v8::Local<'a, v8::Value>,
) -> Result<bool, SqliteError> {
let options = ApplyChangesetOptions::from_value(scope, options)?;
struct HandlerCtx<'a, 'b, 'c> {
scope: &'a mut v8::PinScope<'b, 'c>,
confict: Option<v8::Local<'b, v8::Function>>,
filter: Option<v8::Local<'b, v8::Function>>,
has_caught: bool,
}
unsafe extern "C" fn conflict_handler(
p_ctx: *mut c_void,
e_conflict: i32,
_: *mut libsqlite3_sys::sqlite3_changeset_iter,
) -> i32 {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
let ctx = &mut *(p_ctx as *mut HandlerCtx);
if let Some(conflict) = &mut ctx.confict {
let recv = v8::undefined(ctx.scope).into();
let args = [v8::Integer::new(ctx.scope, e_conflict).into()];
v8::tc_scope!(tc_scope, ctx.scope);
let ret = conflict
.call(tc_scope, recv, &args)
.unwrap_or_else(|| v8::undefined(tc_scope).into());
if tc_scope.has_caught() {
tc_scope.rethrow();
return libsqlite3_sys::SQLITE_CHANGESET_ABORT;
}
const INVALID_VALUE: i32 = -1;
if !ret.is_int32() {
return INVALID_VALUE;
}
let value = ret
.int32_value(tc_scope)
.unwrap_or(libsqlite3_sys::SQLITE_CHANGESET_ABORT);
return value;
}
libsqlite3_sys::SQLITE_CHANGESET_ABORT
}
}
unsafe extern "C" fn filter_handler(
p_ctx: *mut c_void,
z_tab: *const c_char,
) -> i32 {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
let ctx = &mut *(p_ctx as *mut HandlerCtx);
if ctx.has_caught {
return 0;
}
if let Some(filter) = &mut ctx.filter {
let tab = CStr::from_ptr(z_tab).to_str().unwrap();
let recv = v8::undefined(ctx.scope).into();
let args = [v8::String::new(ctx.scope, tab).unwrap().into()];
v8::tc_scope!(tc_scope, ctx.scope);
let ret = filter
.call(tc_scope, recv, &args)
.unwrap_or_else(|| v8::undefined(tc_scope).into());
if tc_scope.has_caught() {
ctx.has_caught = true;
tc_scope.rethrow();
return 0;
}
return ret.boolean_value(tc_scope) as i32;
}
1
}
}
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
let mut ctx = HandlerCtx {
scope,
confict: None,
filter: None,
has_caught: false,
};
if let Some(options) = options {
if let Some(filter) = options.filter {
let filter_cb: v8::Local<v8::Function> = filter
.try_into()
.map_err(|_| SqliteError::InvalidCallback("filter"))?;
ctx.filter = Some(filter_cb);
}
if let Some(on_conflict) = options.on_conflict {
let on_conflict_cb: v8::Local<v8::Function> = on_conflict
.try_into()
.map_err(|_| SqliteError::InvalidCallback("onConflict"))?;
ctx.confict = Some(on_conflict_cb);
}
}
let raw_handle = unsafe { db.handle() };
unsafe {
let r = libsqlite3_sys::sqlite3changeset_apply(
raw_handle,
changeset.len() as i32,
changeset.as_ptr() as *mut _,
Some(filter_handler),
Some(conflict_handler),
&mut ctx as *mut _ as *mut c_void,
);
if r == libsqlite3_sys::SQLITE_OK {
return Ok(true);
} else if r == libsqlite3_sys::SQLITE_ABORT {
return Ok(false);
}
check_error_code2(r)?;
Ok(false)
}
}
fn load_extension(
&self,
state: &mut OpState,
#[validate(validators::path_str)]
#[string]
path: &str,
#[string] entry_point: Option<String>,
) -> Result<(), SqliteError> {
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
if !self.options.allow_extension {
return Err(SqliteError::LoadExensionFailed(
"Cannot load SQLite extensions when allowExtension is not enabled"
.to_string(),
));
}
state.borrow::<PermissionsContainer>().check_ffi_all()?;
let raw_handle = unsafe { db.handle() };
let path_cstring = std::ffi::CString::new(path.as_bytes())?;
let entry_point_cstring =
entry_point.map(|ep| std::ffi::CString::new(ep).unwrap_or_default());
let entry_point_ptr = match &entry_point_cstring {
Some(cstr) => cstr.as_ptr(),
None => std::ptr::null(),
};
let mut err_msg: *mut c_char = std::ptr::null_mut();
let result = unsafe {
let res = libsqlite3_sys::sqlite3_load_extension(
raw_handle,
path_cstring.as_ptr(),
entry_point_ptr,
&mut err_msg,
);
if res != libsqlite3_sys::SQLITE_OK {
let error_message = if !err_msg.is_null() {
let c_str = std::ffi::CStr::from_ptr(err_msg);
let message = c_str.to_string_lossy().into_owned();
libsqlite3_sys::sqlite3_free(err_msg as *mut _);
message
} else {
format!("Failed to load extension with error code: {}", res)
};
return Err(SqliteError::LoadExensionFailed(error_message));
}
res
};
if result == libsqlite3_sys::SQLITE_OK {
Ok(())
} else {
Err(SqliteError::LoadExensionFailed(
"Unknown error loading SQLite extension".to_string(),
))
}
}
#[cppgc]
fn create_session(
&self,
#[scoped] options: OptionUndefined<SessionOptions>,
) -> Result<Session, SqliteError> {
let options = options.0;
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
let raw_handle = unsafe { db.handle() };
let mut raw_session = std::ptr::null_mut();
let mut options = options;
let z_db = options
.as_mut()
.and_then(|options| options.db.take())
.map(|db| CString::new(db).unwrap())
.unwrap_or_else(|| CString::new("main").unwrap());
let r = unsafe {
libsqlite3_sys::sqlite3session_create(
raw_handle,
z_db.as_ptr() as *const _,
&mut raw_session,
)
};
if r != libsqlite3_sys::SQLITE_OK {
return Err(SqliteError::SessionCreateFailed);
}
let table = options
.as_mut()
.and_then(|options| options.table.take())
.map(|table| CString::new(table).unwrap());
let z_table = table.as_ref().map(|table| table.as_ptr()).unwrap_or(null());
let r =
unsafe { libsqlite3_sys::sqlite3session_attach(raw_session, z_table) };
if r != libsqlite3_sys::SQLITE_OK {
return Err(SqliteError::SessionCreateFailed);
}
Ok(Session {
inner: raw_session,
freed: Cell::new(false),
db: self.conn.clone(),
})
}
fn location<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
name_value: v8::Local<'a, v8::Value>,
) -> Result<v8::Local<'a, v8::Value>, SqliteError> {
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
let name = if !name_value.is_undefined() {
if !name_value.is_string() {
return Err(SqliteError::Validation(
validators::Error::InvalidArgType(
"The \"dbName\" argument must be a string.",
),
));
}
name_value.to_rust_string_lossy(scope)
} else {
"main".to_string()
};
let raw_handle = unsafe { db.handle() };
let name_cstring = CString::new(name)?;
let db_filename =
unsafe { sqlite3_db_filename(raw_handle, name_cstring.as_ptr()) };
if db_filename.is_null() {
return Ok(v8::null(scope).into());
}
let filename_cstr = unsafe { CStr::from_ptr(db_filename).to_bytes() };
if filename_cstr.is_empty() {
return Ok(v8::null(scope).into());
}
let filename = v8::String::new_from_utf8(
scope,
filename_cstr,
v8::NewStringType::Normal,
)
.unwrap();
Ok(filename.into())
}
#[fast]
#[validate(is_open)]
fn enable_defensive(
&self,
#[validate(validators::active_bool)] is_enabled: bool,
) -> Result<(), SqliteError> {
let db = self.conn.borrow();
let conn = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
assert!(set_db_config(conn, SQLITE_DBCONFIG_DEFENSIVE, is_enabled));
Ok(())
}
#[fast]
#[validate(is_open)]
#[undefined]
fn set_authorizer<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
callback: v8::Local<'a, v8::Value>,
) -> Result<(), SqliteError> {
let db = self.conn.borrow();
let conn = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
let raw_handle = unsafe { conn.handle() };
{
let mut authorizer_data = self.authorizer_data.borrow_mut();
if let Some(old_data_ptr) = authorizer_data.take() {
unsafe {
free_authorizer_data(old_data_ptr);
}
}
}
if callback.is_null() {
let result = unsafe {
libsqlite3_sys::sqlite3_set_authorizer(
raw_handle,
None,
std::ptr::null_mut(),
)
};
check_error_code(result, raw_handle)?;
return Ok(());
}
let Ok(function) = v8::Local::<v8::Function>::try_from(callback) else {
return Err(
validators::Error::InvalidArgType(
"The \"callback\" argument must be a function or null.",
)
.into(),
);
};
let callback_global = v8::Global::new(scope, function).into_raw();
let context_global =
v8::Global::new(scope, scope.get_current_context()).into_raw();
let data = Box::new(AuthorizerData {
callback: callback_global,
context: context_global,
ignore_next_sqlite_error: Rc::clone(&self.ignore_next_sqlite_error),
});
let data_ptr = Box::into_raw(data);
let result = unsafe {
libsqlite3_sys::sqlite3_set_authorizer(
raw_handle,
Some(authorizer_callback),
data_ptr as *mut c_void,
)
};
if result != libsqlite3_sys::SQLITE_OK {
unsafe {
free_authorizer_data(data_ptr);
}
check_error_code(result, raw_handle)?;
}
*self.authorizer_data.borrow_mut() = Some(data_ptr);
Ok(())
}
#[getter]
fn is_open(&self) -> bool {
self.conn.borrow().is_some()
}
#[getter]
fn is_transaction(&self) -> Result<bool, SqliteError> {
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::AlreadyClosed)?;
let res = unsafe { libsqlite3_sys::sqlite3_get_autocommit(db.handle()) };
Ok(res == 0)
}
#[fast]
#[validate(is_open)]
fn aggregate<'a>(
&self,
scope: &mut v8::PinScope<'a, '_>,
#[validate(validators::name_str)]
#[string]
name: &str,
options: v8::Local<'a, v8::Value>,
) -> Result<(), SqliteError> {
use std::cmp;
let options = AggregateFunctionOption::from_value(scope, options)?;
let mut argc = -1;
if !options.varargs {
v8_static_strings! {
LENGTH = "length",
}
let length_key = LENGTH.v8_string(scope).unwrap();
let step_fn_length = options
.step
.get(scope, length_key.into())
.unwrap()
.int32_value(scope)
.unwrap();
argc = step_fn_length - 1;
let inverse_fn_length = if let Some(inverse_fn) = &options.inverse {
inverse_fn
.get(scope, length_key.into())
.unwrap()
.int32_value(scope)
.unwrap()
} else {
0
};
argc = cmp::max(argc, cmp::max(inverse_fn_length - 1, 0));
}
let mut text_rep = libsqlite3_sys::SQLITE_UTF8;
if options.deterministic {
text_rep |= libsqlite3_sys::SQLITE_DETERMINISTIC;
}
if options.direct_only {
text_rep |= libsqlite3_sys::SQLITE_DIRECTONLY;
}
let db = self.conn.borrow();
let db = db.as_ref().ok_or(SqliteError::InUse)?;
let context =
v8::Global::new(scope, scope.get_current_context()).into_raw();
let start = v8::Global::new(scope, options.start).into_raw();
let step_fn = v8::Global::new(scope, options.step).into_raw();
let inverse_fn = options
.inverse
.map(|inv| v8::Global::new(scope, inv).into_raw());
let final_fn = options
.result
.map(|res| v8::Global::new(scope, res).into_raw());
let custom_aggregate = Box::new(CustomAggregate {
context,
use_big_int_arguments: options.use_big_int_arguments,
start,
step_fn,
inverse_fn,
final_fn,
ignore_next_sqlite_error: Rc::clone(&self.ignore_next_sqlite_error),
});
let custom_aggregate_ptr = Box::into_raw(custom_aggregate);
let raw_handle = unsafe { db.handle() };
let name_cstring = CString::new(name)?;
let (value_callback, inverse_callback) = if options.inverse.is_some() {
(
Some(
custom_aggregate_xvalue
as unsafe extern "C" fn(*mut libsqlite3_sys::sqlite3_context),
),
Some(
custom_aggregate_xinverse
as unsafe extern "C" fn(
*mut libsqlite3_sys::sqlite3_context,
i32,
*mut *mut libsqlite3_sys::sqlite3_value,
),
),
)
} else {
(None, None)
};
let r = unsafe {
sqlite3_create_window_function(
raw_handle,
name_cstring.as_ptr(),
argc,
text_rep,
custom_aggregate_ptr as *mut c_void,
Some(custom_aggregate_xstep),
Some(custom_aggregate_xfinal),
value_callback,
inverse_callback,
Some(custom_aggregate_xdestroy),
)
};
check_error_code(r, raw_handle)?;
Ok(())
}
#[validate(is_open)]
#[cppgc]
fn create_tag_store(
&self,
scope: &mut v8::PinScope<'_, '_>,
#[varargs] args: Option<&v8::FunctionCallbackArguments>,
) -> Result<SQLTagStore, SqliteError> {
let capacity = if let Some(args) = args
&& args.length() > 0
{
let val = args.get(0);
if val.is_number() {
val.uint32_value(scope).unwrap_or(1000)
} else {
1000
}
} else {
1000
};
let db_object = args.map(|a| a.this()).ok_or(SqliteError::AlreadyClosed)?;
Ok(SQLTagStore::create(
self.conn.clone(),
self.statements.clone(),
capacity,
self.options.return_arrays,
self.options.use_big_int_arguments,
v8::Global::new(scope, db_object),
))
}
}
#[repr(C)]
struct AggregateData {
value: Option<NonNull<v8::Value>>,
is_window: bool,
initialized: bool,
}
struct CustomAggregate {
context: NonNull<v8::Context>,
use_big_int_arguments: bool,
start: NonNull<v8::Value>,
step_fn: NonNull<v8::Function>,
inverse_fn: Option<NonNull<v8::Function>>,
final_fn: Option<NonNull<v8::Function>>,
ignore_next_sqlite_error: Rc<Cell<bool>>,
}
enum AggregateStepKind {
Step,
Inverse,
}
unsafe fn get_aggregate_data(
ctx: *mut libsqlite3_sys::sqlite3_context,
custom_aggregate: &CustomAggregate,
scope: &mut v8::PinScope<'_, '_>,
) -> Option<*mut AggregateData> {
unsafe {
let agg_ptr = libsqlite3_sys::sqlite3_aggregate_context(
ctx,
std::mem::size_of::<AggregateData>() as i32,
) as *mut AggregateData;
if agg_ptr.is_null() {
sqlite_result_error(ctx, "Failed to allocate aggregate context");
return None;
}
let agg = &mut *agg_ptr;
if !agg.initialized {
let start_local: v8::Local<v8::Value> =
std::mem::transmute(custom_aggregate.start.as_ptr());
let start_value = if start_local.is_function() {
let start_fn: v8::Local<v8::Function> = start_local.try_into().unwrap();
let recv = v8::null(scope).into();
match start_fn.call(scope, recv, &[]) {
Some(val) => val,
None => {
custom_aggregate.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
return None;
}
}
} else {
start_local
};
let global = v8::Global::new(scope, start_value);
agg.value = Some(global.into_raw());
agg.initialized = true;
agg.is_window = false;
}
Some(agg_ptr)
}
}
unsafe fn custom_aggregate_step_base(
ctx: *mut libsqlite3_sys::sqlite3_context,
argc: i32,
argv: *mut *mut libsqlite3_sys::sqlite3_value,
kind: AggregateStepKind,
) {
unsafe {
let data_ptr =
libsqlite3_sys::sqlite3_user_data(ctx) as *mut CustomAggregate;
if data_ptr.is_null() {
sqlite_result_error(
ctx,
"Internal error: missing custom aggregate context",
);
return;
}
let data = &*data_ptr;
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
v8::tc_scope!(tc_scope, scope);
let Some(agg_ptr) = get_aggregate_data(ctx, data, tc_scope) else {
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
}
return;
};
let agg = &mut *agg_ptr;
let step_fn = match kind {
AggregateStepKind::Step => {
let step_fn: v8::Local<v8::Function> =
std::mem::transmute(data.step_fn.as_ptr());
step_fn
}
AggregateStepKind::Inverse => {
if let Some(inverse_ptr) = data.inverse_fn {
let inverse_fn: v8::Local<v8::Function> =
std::mem::transmute(inverse_ptr.as_ptr());
inverse_fn
} else {
sqlite_result_error(ctx, "Internal error: inverse function not set");
return;
}
}
};
let argc_len = usize::try_from(argc).unwrap_or(0);
let args_slice = if argc_len == 0 {
&[]
} else {
std::slice::from_raw_parts(argv, argc_len)
};
let current_value = if let Some(value_ptr) = agg.value {
let global = v8::Global::from_raw(tc_scope, value_ptr);
let local = v8::Local::new(tc_scope, &global);
std::mem::forget(global);
local
} else {
v8::undefined(tc_scope).into()
};
let mut js_args = Vec::with_capacity(args_slice.len() + 1);
js_args.push(current_value);
for &value_ptr in args_slice {
if let Some(arg) =
sqlite_value_to_v8(tc_scope, value_ptr, data.use_big_int_arguments)
{
js_args.push(arg);
} else {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
return;
}
}
let recv = v8::undefined(tc_scope).into();
let result = step_fn
.call(tc_scope, recv, &js_args)
.unwrap_or_else(|| v8::undefined(tc_scope).into());
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
return;
}
if let Some(old_ptr) = agg.value {
let _ = v8::Global::from_raw(tc_scope, old_ptr);
}
let global = v8::Global::new(tc_scope, result);
agg.value = Some(global.into_raw());
}
}
unsafe fn custom_aggregate_value_base(
ctx: *mut libsqlite3_sys::sqlite3_context,
is_final: bool,
) {
unsafe {
let data_ptr =
libsqlite3_sys::sqlite3_user_data(ctx) as *mut CustomAggregate;
if data_ptr.is_null() {
sqlite_result_error(
ctx,
"Internal error: missing custom aggregate context",
);
return;
}
let data = &*data_ptr;
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
v8::tc_scope!(tc_scope, scope);
let Some(agg_ptr) = get_aggregate_data(ctx, data, tc_scope) else {
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
}
return;
};
let agg = &mut *agg_ptr;
if !is_final {
agg.is_window = true;
} else if agg.is_window {
if let Some(value_ptr) = agg.value.take() {
let _ = v8::Global::from_raw(tc_scope, value_ptr);
}
return;
}
let current_value = if let Some(value_ptr) = agg.value {
let global = v8::Global::from_raw(tc_scope, value_ptr);
let local = v8::Local::new(tc_scope, &global);
if is_final {
agg.value = None;
} else {
std::mem::forget(global);
}
local
} else {
v8::undefined(tc_scope).into()
};
let result = if let Some(result_fn_ptr) = data.final_fn {
let result_fn: v8::Local<v8::Function> =
std::mem::transmute(result_fn_ptr.as_ptr());
let ret = result_fn
.call(tc_scope, v8::null(tc_scope).into(), &[current_value])
.unwrap_or_else(|| v8::undefined(tc_scope).into());
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
return;
}
ret
} else {
current_value
};
js_value_to_sqlite(tc_scope, ctx, result);
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
}
}
}
unsafe extern "C" fn custom_aggregate_xstep(
ctx: *mut libsqlite3_sys::sqlite3_context,
argc: i32,
argv: *mut *mut libsqlite3_sys::sqlite3_value,
) {
unsafe {
custom_aggregate_step_base(ctx, argc, argv, AggregateStepKind::Step);
}
}
unsafe extern "C" fn custom_aggregate_xinverse(
ctx: *mut libsqlite3_sys::sqlite3_context,
argc: i32,
argv: *mut *mut libsqlite3_sys::sqlite3_value,
) {
unsafe {
custom_aggregate_step_base(ctx, argc, argv, AggregateStepKind::Inverse);
}
}
unsafe extern "C" fn custom_aggregate_xfinal(
ctx: *mut libsqlite3_sys::sqlite3_context,
) {
unsafe {
custom_aggregate_value_base(ctx, true);
}
}
unsafe extern "C" fn custom_aggregate_xvalue(
ctx: *mut libsqlite3_sys::sqlite3_context,
) {
unsafe {
custom_aggregate_value_base(ctx, false);
}
}
unsafe extern "C" fn custom_aggregate_xdestroy(data: *mut c_void) {
unsafe {
let data = Box::from_raw(data as *mut CustomAggregate);
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
let _ = v8::Global::from_raw(scope, data.context);
let _ = v8::Global::from_raw(scope, data.start);
let _ = v8::Global::from_raw(scope, data.step_fn);
if let Some(inverse_ptr) = data.inverse_fn {
let _ = v8::Global::from_raw(scope, inverse_ptr);
}
if let Some(final_ptr) = data.final_fn {
let _ = v8::Global::from_raw(scope, final_ptr);
}
}
}
impl DatabaseSync {
fn consume_ignore_next_sqlite_error(&self) -> bool {
self.ignore_next_sqlite_error.replace(false)
}
}
struct CustomFunctionData {
callback: NonNull<v8::Function>,
context: NonNull<v8::Context>,
use_big_int_arguments: bool,
ignore_next_sqlite_error: Rc<Cell<bool>>,
}
struct AuthorizerData {
callback: NonNull<v8::Function>,
context: NonNull<v8::Context>,
ignore_next_sqlite_error: Rc<Cell<bool>>,
}
unsafe extern "C" fn custom_function_handler(
ctx: *mut libsqlite3_sys::sqlite3_context,
argc: i32,
argv: *mut *mut libsqlite3_sys::sqlite3_value,
) {
unsafe {
let data_ptr =
libsqlite3_sys::sqlite3_user_data(ctx) as *mut CustomFunctionData;
if data_ptr.is_null() {
sqlite_result_error(
ctx,
"Internal error: missing custom function context",
);
return;
}
let data = &*data_ptr;
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
v8::tc_scope!(tc_scope, scope);
let function_local: v8::Local<v8::Function> =
std::mem::transmute(data.callback.as_ptr());
let argc_len = usize::try_from(argc).unwrap_or(0);
let args_slice = if argc_len == 0 {
&[]
} else {
std::slice::from_raw_parts(argv, argc_len)
};
let mut js_args = Vec::with_capacity(args_slice.len());
for &value_ptr in args_slice {
if let Some(arg) =
sqlite_value_to_v8(tc_scope, value_ptr, data.use_big_int_arguments)
{
js_args.push(arg);
} else {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
return;
}
}
let recv = v8::undefined(tc_scope).into();
let result = function_local.call(tc_scope, recv, &js_args);
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
return;
}
if let Some(value) = result {
js_value_to_sqlite(tc_scope, ctx, value);
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
sqlite_result_error(ctx, "");
tc_scope.rethrow();
}
}
}
}
unsafe extern "C" fn custom_function_destroy(data: *mut c_void) {
unsafe {
let data = Box::from_raw(data as *mut CustomFunctionData);
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
let _ = v8::Global::from_raw(scope, data.callback);
let _ = v8::Global::from_raw(scope, data.context);
}
}
fn sqlite_value_to_v8<'a>(
scope: &mut v8::PinScope<'a, '_>,
value: *mut libsqlite3_sys::sqlite3_value,
use_big_int_arguments: bool,
) -> Option<v8::Local<'a, v8::Value>> {
unsafe {
match libsqlite3_sys::sqlite3_value_type(value) {
libsqlite3_sys::SQLITE_INTEGER => {
let val = libsqlite3_sys::sqlite3_value_int64(value);
if use_big_int_arguments {
Some(v8::BigInt::new_from_i64(scope, val).into())
} else if (-MAX_SAFE_JS_INTEGER..=MAX_SAFE_JS_INTEGER).contains(&val) {
Some(v8::Number::new(scope, val as f64).into())
} else {
let msg = format!(
"Value is too large to be represented as a JavaScript number: {}",
val
);
throw_range_error(scope, &msg);
None
}
}
libsqlite3_sys::SQLITE_FLOAT => {
let val = libsqlite3_sys::sqlite3_value_double(value);
Some(v8::Number::new(scope, val).into())
}
libsqlite3_sys::SQLITE_TEXT => {
let len = libsqlite3_sys::sqlite3_value_bytes(value) as usize;
if len == 0 {
let text =
v8::String::new_from_utf8(scope, b"", v8::NewStringType::Normal)
.unwrap();
Some(text.into())
} else {
let ptr = libsqlite3_sys::sqlite3_value_text(value);
let slice = std::slice::from_raw_parts(ptr, len);
let text =
v8::String::new_from_utf8(scope, slice, v8::NewStringType::Normal)
.unwrap();
Some(text.into())
}
}
libsqlite3_sys::SQLITE_BLOB => {
let len = libsqlite3_sys::sqlite3_value_bytes(value);
if len == 0 {
let ab = v8::ArrayBuffer::new(scope, 0);
let view = v8::Uint8Array::new(scope, ab, 0, 0).unwrap();
Some(view.into())
} else {
let ptr = libsqlite3_sys::sqlite3_value_blob(value) as *const u8;
let slice = std::slice::from_raw_parts(ptr, len as usize);
let backing =
v8::ArrayBuffer::new_backing_store_from_vec(slice.to_vec())
.make_shared();
let ab = v8::ArrayBuffer::with_backing_store(scope, &backing);
let view = v8::Uint8Array::new(scope, ab, 0, len as usize).unwrap();
Some(view.into())
}
}
libsqlite3_sys::SQLITE_NULL => Some(v8::null(scope).into()),
_ => Some(v8::undefined(scope).into()),
}
}
}
fn js_value_to_sqlite(
scope: &mut v8::PinScope<'_, '_>,
ctx: *mut libsqlite3_sys::sqlite3_context,
value: v8::Local<v8::Value>,
) {
unsafe {
if value.is_null_or_undefined() {
libsqlite3_sys::sqlite3_result_null(ctx);
return;
}
if value.is_number() {
let number = value.number_value(scope).unwrap_or(0f64);
libsqlite3_sys::sqlite3_result_double(ctx, number);
return;
}
if value.is_string() {
let text = value.to_rust_string_lossy(scope);
libsqlite3_sys::sqlite3_result_text(
ctx,
text.as_ptr() as *const _,
text.len() as i32,
libsqlite3_sys::SQLITE_TRANSIENT(),
);
return;
}
if value.is_array_buffer_view() {
let view: v8::Local<v8::ArrayBufferView> = value.try_into().unwrap();
let mut data = view.data();
let mut size = view.byte_length();
if data.is_null() {
static EMPTY: [u8; 0] = [];
data = EMPTY.as_ptr() as *mut _;
size = 0;
}
libsqlite3_sys::sqlite3_result_blob(
ctx,
data,
size as i32,
libsqlite3_sys::SQLITE_TRANSIENT(),
);
return;
}
if value.is_big_int() {
let bigint: v8::Local<v8::BigInt> = value.try_into().unwrap();
let (int_value, lossless) = bigint.i64_value();
if !lossless {
throw_range_error(scope, "BigInt value is too large for SQLite");
sqlite_result_error(ctx, "");
return;
}
libsqlite3_sys::sqlite3_result_int64(ctx, int_value);
return;
}
if value.is_promise() {
sqlite_result_error(
ctx,
"Asynchronous user-defined functions are not supported",
);
return;
}
sqlite_result_error(
ctx,
"Returned JavaScript value cannot be converted to a SQLite value",
);
}
}
fn sqlite_result_error(
ctx: *mut libsqlite3_sys::sqlite3_context,
message: &str,
) {
let msg = CString::new(message).unwrap();
unsafe {
libsqlite3_sys::sqlite3_result_error(
ctx,
msg.as_ptr(),
msg.as_bytes().len() as i32,
);
}
}
macro_rules! nullable_cstring_to_v8_value {
($scope:expr, $ptr:expr) => {{
if ($ptr).is_null() {
v8::null($scope).into()
} else {
let cstr = CStr::from_ptr($ptr);
v8::String::new_from_utf8(
$scope,
cstr.to_bytes(),
v8::NewStringType::Normal,
)
.map(|s| s.into())
.unwrap_or_else(|| v8::null($scope).into())
}
}};
}
unsafe extern "C" fn authorizer_callback(
user_data: *mut c_void,
action_code: i32,
param1: *const c_char,
param2: *const c_char,
param3: *const c_char,
param4: *const c_char,
) -> i32 {
unsafe {
let data_ptr = user_data as *mut AuthorizerData;
if data_ptr.is_null() {
return libsqlite3_sys::SQLITE_DENY;
}
let data = &*data_ptr;
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
let result: Option<v8::Local<'_, v8::Value>>;
{
v8::tc_scope!(tc_scope, scope);
let function_local: v8::Local<v8::Function> =
std::mem::transmute(data.callback.as_ptr());
let action_code_js = v8::Integer::new(tc_scope, action_code).into();
let param1_js: v8::Local<v8::Value> =
nullable_cstring_to_v8_value!(tc_scope, param1);
let param2_js: v8::Local<v8::Value> =
nullable_cstring_to_v8_value!(tc_scope, param2);
let param3_js: v8::Local<v8::Value> =
nullable_cstring_to_v8_value!(tc_scope, param3);
let param4_js: v8::Local<v8::Value> =
nullable_cstring_to_v8_value!(tc_scope, param4);
let js_args =
[action_code_js, param1_js, param2_js, param3_js, param4_js];
let recv = v8::undefined(tc_scope).into();
result = function_local.call(tc_scope, recv, &js_args);
if tc_scope.has_caught() {
data.ignore_next_sqlite_error.set(true);
tc_scope.rethrow();
return libsqlite3_sys::SQLITE_DENY;
}
}
let Some(value) = result else {
data.ignore_next_sqlite_error.set(true);
return libsqlite3_sys::SQLITE_DENY;
};
if !value.is_int32() {
let message = v8::String::new(
scope,
"Authorizer callback must return an integer authorization code",
)
.unwrap();
let err = v8::Exception::type_error(scope, message);
data.ignore_next_sqlite_error.set(true);
scope.throw_exception(err);
return libsqlite3_sys::SQLITE_DENY;
}
let int_result = value
.int32_value(scope)
.unwrap_or(libsqlite3_sys::SQLITE_DENY);
if int_result != libsqlite3_sys::SQLITE_OK
&& int_result != libsqlite3_sys::SQLITE_DENY
&& int_result != libsqlite3_sys::SQLITE_IGNORE
{
let message = v8::String::new(
scope,
"Authorizer callback returned a invalid authorization code",
)
.unwrap();
let err = v8::Exception::range_error(scope, message);
data.ignore_next_sqlite_error.set(true);
scope.throw_exception(err);
return libsqlite3_sys::SQLITE_DENY;
}
int_result
}
}
unsafe fn free_authorizer_data(data_ptr: *mut AuthorizerData) {
if data_ptr.is_null() {
return;
}
unsafe {
let data = Box::from_raw(data_ptr);
let context_local: v8::Local<v8::Context> =
std::mem::transmute(data.context.as_ptr());
v8::callback_scope!(unsafe cb_scope, context_local);
v8::scope!(scope, cb_scope);
let _ = v8::Global::from_raw(scope, data.callback);
let _ = v8::Global::from_raw(scope, data.context);
}
}
fn throw_range_error(scope: &mut v8::PinScope<'_, '_>, message: &str) {
let msg = v8::String::new(scope, message).unwrap();
let error = v8::Exception::range_error(scope, msg);
v8_static_strings!(CODE = "code", ERR_OUT_OF_RANGE = "ERR_OUT_OF_RANGE");
let code_key = CODE.v8_string(scope).unwrap();
let code_value = ERR_OUT_OF_RANGE.v8_string(scope).unwrap();
let error_obj: v8::Local<v8::Object> = error.try_into().unwrap();
error_obj
.set(scope, code_key.into(), code_value.into())
.unwrap();
scope.throw_exception(error);
}