Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
330 changes: 281 additions & 49 deletions ext/node/ops/sqlite/database.rs

Large diffs are not rendered by default.

231 changes: 114 additions & 117 deletions ext/node/ops/sqlite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,182 +6,179 @@ mod statement;
mod validators;

pub use database::DatabaseSync;
use rusqlite::ffi as libsqlite3_sys;
pub use session::Session;
pub use statement::StatementSync;

#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum SqliteError {
#[class(inherit)]
#[error(transparent)]
#[property("code" = self.code())]
Permission(#[from] deno_permissions::PermissionCheckError),
#[class(generic)]
#[error(transparent)]
#[property("code" = self.code())]
SqliteError(#[from] rusqlite::Error),
#[class(generic)]
#[error("{0}")]
SqliteSysError(String),
#[error("{message}")]
#[property("code" = self.code())]
#[property("errstr" = self.errstr())]
SqliteSysError {
message: String,
errstr: String,
#[property]
errcode: f64,
},
#[class(generic)]
#[error("Database is already in use")]
#[property("code" = self.code())]
InUse,
#[class(generic)]
#[error("Failed to load SQLite extension: {0}")]
#[property("code" = self.code())]
LoadExensionFailed(String),
#[class(generic)]
#[error("Failed to bind parameter. {0}")]
#[property("code" = self.code())]
FailedBind(&'static str),
#[class(type)]
#[error("Provided value cannot be bound to SQLite parameter {0}.")]
#[property("code" = self.code())]
InvalidBindType(i32),
#[class(type)]
#[error("{0}")]
#[property("code" = self.code())]
InvalidBindValue(&'static str),
#[class(generic)]
#[error("Cannot create bare named parameter '{0}' because of conflicting names '{1}' and '{2}'.")]
#[property("code" = self.code())]
DuplicateNamedParameter(String, String, String),
#[class(generic)]
#[error("Unknown column type")]
#[error("Unknown named parameter '{0}'")]
#[property("code" = self.code())]
UnknownNamedParameter(String),
#[class(generic)]
#[error("unknown column type")]
#[property("code" = self.code())]
UnknownColumnType,
#[class(generic)]
#[error("Failed to get SQL")]
#[error("failed to get SQL")]
#[property("code" = self.code())]
GetSqlFailed,
#[class(generic)]
#[error("Database is already closed")]
#[error("database is not open")]
#[property("code" = self.code())]
AlreadyClosed,
#[class(generic)]
#[error("Database is already open")]
#[error("database is already open")]
#[property("code" = self.code())]
AlreadyOpen,
#[class(generic)]
#[error("Failed to prepare statement")]
#[error("failed to prepare statement")]
#[property("code" = self.code())]
PrepareFailed,
#[class(generic)]
#[error("Failed to create session")]
#[error("failed to create session")]
#[property("code" = self.code())]
SessionCreateFailed,
#[class(generic)]
#[error("Failed to retrieve changeset")]
#[error("failed to retrieve changeset")]
#[property("code" = self.code())]
SessionChangesetFailed,
#[class(generic)]
#[error("Session is already closed")]
#[error("session is not open")]
#[property("code" = self.code())]
SessionClosed,
#[class(generic)]
#[error("Invalid constructor")]
#[error("Illegal constructor")]
#[property("code" = self.code())]
InvalidConstructor,
#[class(generic)]
#[error("Expanded SQL text would exceed configured limits")]
#[property("code" = self.code())]
InvalidExpandedSql,
#[class(range)]
#[error("The value of column {0} is too large to be represented as a JavaScript number: {1}")]
#[property("code" = self.code())]
NumberTooLarge(i32, i64),
#[class(generic)]
#[error("Failed to apply changeset")]
ChangesetApplyFailed,
#[class(type)]
#[error("Invalid callback: {0}")]
#[property("code" = self.code())]
InvalidCallback(&'static str),
#[class(type)]
#[error("FromUtf8Error: {0}")]
FromUtf8Error(#[from] std::ffi::NulError),
#[property("code" = self.code())]
FromNullError(#[from] std::ffi::NulError),
#[class(type)]
#[error("FromUtf8Error: {0}")]
#[property("code" = self.code())]
FromUtf8Error(#[from] std::str::Utf8Error),
#[class(inherit)]
#[error(transparent)]
#[property("code" = self.code())]
Validation(#[from] validators::Error),
}

pub trait SqliteResultExt<T> {
fn with_enhanced_errors(
self,
db: &rusqlite::Connection,
) -> Result<T, SqliteError>;
#[derive(Debug, PartialEq, Eq)]
#[allow(non_camel_case_types)]
enum ErrorCode {
ERR_SQLITE_ERROR,
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_STATE,
ERR_OUT_OF_RANGE,
ERR_LOAD_SQLITE_EXTENSION,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
}

impl<T> SqliteResultExt<T> for Result<T, rusqlite::Error> {
fn with_enhanced_errors(
self,
db: &rusqlite::Connection,
) -> Result<T, SqliteError> {
impl std::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

impl ErrorCode {
pub fn as_str(&self) -> &str {
match self {
Ok(value) => Ok(value),
Err(error) => {
// SAFETY: lifetime of the connection is guaranteed by the rusqlite API.
let handle = unsafe { db.handle() };
// SAFETY: error conversion does not perform additional dereferencing beyond what is documented.
Err(unsafe { SqliteError::from_rusqlite_with_details(error, handle) })
}
Self::ERR_SQLITE_ERROR => "ERR_SQLITE_ERROR",
Self::ERR_ILLEGAL_CONSTRUCTOR => "ERR_ILLEGAL_CONSTRUCTOR",
Self::ERR_INVALID_ARG_TYPE => "ERR_INVALID_ARG_TYPE",
Self::ERR_INVALID_ARG_VALUE => "ERR_INVALID_ARG_VALUE",
Self::ERR_INVALID_STATE => "ERR_INVALID_STATE",
Self::ERR_OUT_OF_RANGE => "ERR_OUT_OF_RANGE",
Self::ERR_LOAD_SQLITE_EXTENSION => "ERR_LOAD_SQLITE_EXTENSION",
}
}
}

impl SqliteError {
pub const ERROR_CODE_GENERIC: i32 = 1;

pub const ERROR_STR_UNKNOWN: &str = "unknown error";

pub fn create_error(message: &str, code: &str) -> Self {
let encoded_message = format!("{}\n {{\n code: '{}'\n}}", message, code);

let custom_error = rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error {
code: rusqlite::ErrorCode::Unknown,
extended_code: Self::ERROR_CODE_GENERIC,
},
Some(encoded_message),
);

SqliteError::SqliteError(custom_error)
impl From<ErrorCode> for deno_error::PropertyValue {
fn from(code: ErrorCode) -> Self {
deno_error::PropertyValue::from(code.as_str().to_string())
}
}

pub fn create_enhanced_error<T>(
extended_code: i32,
message: &str,
db_handle: Option<*mut libsqlite3_sys::sqlite3>,
) -> Result<T, Self> {
let rusqlite_error = rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error {
code: rusqlite::ErrorCode::Unknown,
extended_code,
},
Some(message.to_string()),
);

let handle = db_handle.unwrap_or(std::ptr::null_mut());
// SAFETY: error conversion does not perform additional dereferencing beyond what is documented.
Err(unsafe {
SqliteError::from_rusqlite_with_details(rusqlite_error, handle)
})
impl SqliteError {
fn errstr(&self) -> String {
match self {
Self::SqliteSysError { errstr, .. } => errstr.clone(),
_ => unreachable!(),
}
}

/// Creates a `SqliteError` from a rusqlite error and a raw SQLite handle.
///
/// # Safety
///
/// Caller must ensure `handle` is non-null and points to a valid, initialized sqlite3 instance.
pub unsafe fn from_rusqlite_with_details(
error: rusqlite::Error,
handle: *mut libsqlite3_sys::sqlite3,
) -> Self {
let message = error.to_string();

let err_code = match &error {
rusqlite::Error::SqliteFailure(ffi_error, _) => ffi_error.code as i32,
_ => {
if !handle.is_null() {
// SAFETY: We've verified that handle is not null in the previous condition.
unsafe { libsqlite3_sys::sqlite3_errcode(handle) }
} else {
Self::ERROR_CODE_GENERIC
}
}
};

// SAFETY: We're using sqlite3_errstr which returns a static string.
let err_str = unsafe {
let ptr = libsqlite3_sys::sqlite3_errstr(err_code);
if !ptr.is_null() {
std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned()
} else {
Self::ERROR_STR_UNKNOWN.to_string()
}
};

let encoded_message = format!(
"{}\n {{\n code: 'ERR_SQLITE_ERROR',\n errcode: {},\n errstr: '{}'\n}}",
message,
err_code,
err_str
);

let custom_error = rusqlite::Error::SqliteFailure(
rusqlite::ffi::Error {
code: rusqlite::ErrorCode::Unknown,
extended_code: err_code,
},
Some(encoded_message),
);

SqliteError::SqliteError(custom_error)
fn code(&self) -> ErrorCode {
match self {
Self::InvalidConstructor => ErrorCode::ERR_ILLEGAL_CONSTRUCTOR,
Self::InvalidBindType(_) => ErrorCode::ERR_INVALID_ARG_TYPE,
Self::InvalidBindValue(_) => ErrorCode::ERR_INVALID_ARG_VALUE,
Self::FailedBind(_)
| Self::UnknownNamedParameter(_)
| Self::DuplicateNamedParameter(..)
| Self::AlreadyClosed
| Self::InUse
| Self::AlreadyOpen => ErrorCode::ERR_INVALID_STATE,
Self::NumberTooLarge(_, _) => ErrorCode::ERR_OUT_OF_RANGE,
Self::LoadExensionFailed(_) => ErrorCode::ERR_LOAD_SQLITE_EXTENSION,
_ => ErrorCode::ERR_SQLITE_ERROR,
}
}
}
Loading
Loading