Skip to content

Allow static strings in SQLiteError #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 26, 2025
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
3 changes: 1 addition & 2 deletions crates/core/src/crud_vtab.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
extern crate alloc;

use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use const_format::formatcp;
use core::ffi::{c_char, c_int, c_void, CStr};
Expand Down Expand Up @@ -86,7 +85,7 @@ impl VirtualTable {
let current_tx = self
.current_tx
.as_mut()
.ok_or_else(|| SQLiteError(ResultCode::MISUSE, Some(String::from("No tx_id"))))?;
.ok_or_else(|| SQLiteError::misuse("No tx_id"))?;
let db = self.db;

if self.state.is_in_sync_local.load(Ordering::Relaxed) {
Expand Down
31 changes: 23 additions & 8 deletions crates/core/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use alloc::{
borrow::Cow,
format,
string::{String, ToString},
};
Expand All @@ -8,20 +9,34 @@ use sqlite_nostd::{context, sqlite3, Connection, Context, ResultCode};
use crate::bson::BsonError;

#[derive(Debug)]
pub struct SQLiteError(pub ResultCode, pub Option<String>);
pub struct SQLiteError(pub ResultCode, pub Option<Cow<'static, str>>);

impl SQLiteError {
pub fn with_description(code: ResultCode, message: impl Into<Cow<'static, str>>) -> Self {
Self(code, Some(message.into()))
}

pub fn misuse(message: impl Into<Cow<'static, str>>) -> Self {
Self::with_description(ResultCode::MISUSE, message)
}
}

impl core::fmt::Display for SQLiteError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:?}", self)
write!(f, "SQLiteError: {:?}", self.0)?;
if let Some(desc) = &self.1 {
write!(f, ", desc: {}", desc)?;
}
Ok(())
}
}

impl SQLiteError {
pub fn apply_to_ctx(self, description: &str, ctx: *mut context) {
let SQLiteError(code, message) = self;

if message.is_some() {
ctx.result_error(&format!("{:} {:}", description, message.unwrap()));
if let Some(msg) = message {
ctx.result_error(&format!("{:} {:}", description, msg));
} else {
let error = ctx.db_handle().errmsg().unwrap();
if error == "not an error" {
Expand All @@ -47,7 +62,7 @@ impl<T> PSResult<T> for Result<T, ResultCode> {
if message == "not an error" {
Err(SQLiteError(code, None))
} else {
Err(SQLiteError(code, Some(message)))
Err(SQLiteError(code, Some(message.into())))
}
} else if let Ok(r) = self {
Ok(r)
Expand All @@ -65,18 +80,18 @@ impl From<ResultCode> for SQLiteError {

impl From<serde_json::Error> for SQLiteError {
fn from(value: serde_json::Error) -> Self {
SQLiteError(ResultCode::ABORT, Some(value.to_string()))
SQLiteError::with_description(ResultCode::ABORT, value.to_string())
}
}

impl From<core::fmt::Error> for SQLiteError {
fn from(value: core::fmt::Error) -> Self {
SQLiteError(ResultCode::INTERNAL, Some(format!("{}", value)))
SQLiteError::with_description(ResultCode::INTERNAL, format!("{}", value))
}
}

impl From<BsonError> for SQLiteError {
fn from(value: BsonError) -> Self {
SQLiteError(ResultCode::ERROR, Some(value.to_string()))
SQLiteError::with_description(ResultCode::ERROR, value.to_string())
}
}
5 changes: 2 additions & 3 deletions crates/core/src/kv.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
extern crate alloc;

use alloc::format;
use alloc::string::{String, ToString};
use core::ffi::c_int;

Expand Down Expand Up @@ -30,9 +29,9 @@ pub fn client_id(db: *mut sqlite::sqlite3) -> Result<String, SQLiteError> {
let client_id = statement.column_text(0)?;
Ok(client_id.to_string())
} else {
Err(SQLiteError(
Err(SQLiteError::with_description(
ResultCode::ABORT,
Some(format!("No client_id found in ps_kv")),
"No client_id found in ps_kv",
))
}
}
Expand Down
16 changes: 8 additions & 8 deletions crates/core/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ CREATE TABLE IF NOT EXISTS ps_migration(id INTEGER PRIMARY KEY, down_migrations
for sql in down_sql {
let rs = local_db.exec_safe(&sql);
if let Err(code) = rs {
return Err(SQLiteError(
return Err(SQLiteError::with_description(
code,
Some(format!(
format!(
"Down migration failed for {:} {:} {:}",
current_version,
sql,
local_db
.errmsg()
.unwrap_or(String::from("Conversion error"))
)),
),
));
}
}
Expand All @@ -73,20 +73,20 @@ CREATE TABLE IF NOT EXISTS ps_migration(id INTEGER PRIMARY KEY, down_migrations
current_version_stmt.reset()?;
let rc = current_version_stmt.step()?;
if rc != ResultCode::ROW {
return Err(SQLiteError(
return Err(SQLiteError::with_description(
rc,
Some("Down migration failed - could not get version".to_string()),
"Down migration failed - could not get version",
));
}
let new_version = current_version_stmt.column_int(0);
if new_version >= current_version {
// Database down from version $currentVersion to $version failed - version not updated after dow migration
return Err(SQLiteError(
return Err(SQLiteError::with_description(
ResultCode::ABORT,
Some(format!(
format!(
"Down migration failed - version not updated from {:}",
current_version
)),
),
));
}
current_version = new_version;
Expand Down
21 changes: 4 additions & 17 deletions crates/core/src/sync/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use core::ffi::{c_int, c_void};
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::{string::String, vec::Vec};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -141,10 +140,7 @@ pub fn register(db: *mut sqlite::sqlite3, state: Arc<DatabaseState>) -> Result<(
};

if op.value_type() != ColumnType::Text {
return Err(SQLiteError(
ResultCode::MISUSE,
Some("First argument must be a string".to_string()),
));
return Err(SQLiteError::misuse("First argument must be a string"));
}

let op = op.text();
Expand All @@ -161,29 +157,20 @@ pub fn register(db: *mut sqlite::sqlite3, state: Arc<DatabaseState>) -> Result<(
data: if payload.value_type() == ColumnType::Text {
payload.text()
} else {
return Err(SQLiteError(
ResultCode::MISUSE,
Some("Second argument must be a string".to_string()),
));
return Err(SQLiteError::misuse("Second argument must be a string"));
},
}),
"line_binary" => SyncControlRequest::SyncEvent(SyncEvent::BinaryLine {
data: if payload.value_type() == ColumnType::Blob {
payload.blob()
} else {
return Err(SQLiteError(
ResultCode::MISUSE,
Some("Second argument must be a byte array".to_string()),
));
return Err(SQLiteError::misuse("Second argument must be a byte array"));
},
}),
"refreshed_token" => SyncControlRequest::SyncEvent(SyncEvent::DidRefreshToken),
"completed_upload" => SyncControlRequest::SyncEvent(SyncEvent::UploadFinished),
_ => {
return Err(SQLiteError(
ResultCode::MISUSE,
Some("Unknown operation".to_string()),
))
return Err(SQLiteError::misuse("Unknown operation"));
}
};

Expand Down
25 changes: 7 additions & 18 deletions crates/core/src/sync/streaming_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ impl SyncClient {
let mut active = ActiveEvent::new(sync_event);

let ClientState::IterationActive(handle) = &mut self.state else {
return Err(SQLiteError(
ResultCode::MISUSE,
Some("No iteration is active".to_string()),
));
return Err(SQLiteError::misuse("No iteration is active"));
};

match handle.run(&mut active) {
Expand Down Expand Up @@ -308,11 +305,9 @@ impl StreamingSyncIteration {
}
SyncLine::CheckpointDiff(diff) => {
let Some(target) = target.target_checkpoint_mut() else {
return Err(SQLiteError(
return Err(SQLiteError::with_description(
ResultCode::ABORT,
Some(
"Received checkpoint_diff without previous checkpoint".to_string(),
),
"Received checkpoint_diff without previous checkpoint",
));
};

Expand All @@ -329,12 +324,9 @@ impl StreamingSyncIteration {
}
SyncLine::CheckpointComplete(_) => {
let Some(target) = target.target_checkpoint_mut() else {
return Err(SQLiteError(
return Err(SQLiteError::with_description(
ResultCode::ABORT,
Some(
"Received checkpoint complete without previous checkpoint"
.to_string(),
),
"Received checkpoint complete without previous checkpoint",
));
};
let result =
Expand Down Expand Up @@ -374,12 +366,9 @@ impl StreamingSyncIteration {
SyncLine::CheckpointPartiallyComplete(complete) => {
let priority = complete.priority;
let Some(target) = target.target_checkpoint_mut() else {
return Err(SQLiteError(
return Err(SQLiteError::with_description(
ResultCode::ABORT,
Some(
"Received checkpoint complete without previous checkpoint"
.to_string(),
),
"Received checkpoint complete without previous checkpoint",
));
};
let result = self.adapter.sync_local(
Expand Down
26 changes: 11 additions & 15 deletions crates/core/src/sync_local.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::collections::btree_map::BTreeMap;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::string::String;
use alloc::vec::Vec;
use serde::Deserialize;

Expand Down Expand Up @@ -504,15 +504,12 @@ impl<'a> PreparedPendingStatement<'a> {
) -> Result<Self, SQLiteError> {
let stmt = db.prepare_v2(&pending.sql)?;
if stmt.bind_parameter_count() as usize != pending.params.len() {
return Err(SQLiteError(
ResultCode::MISUSE,
Some(format!(
"Statement {} has {} parameters, but {} values were provided as sources.",
&pending.sql,
stmt.bind_parameter_count(),
pending.params.len(),
)),
));
return Err(SQLiteError::misuse(format!(
"Statement {} has {} parameters, but {} values were provided as sources.",
&pending.sql,
stmt.bind_parameter_count(),
pending.params.len(),
)));
}

// TODO: Compare number of variables / other validity checks?
Expand All @@ -534,9 +531,9 @@ impl<'a> PreparedPendingStatement<'a> {
}
PendingStatementValue::Column(column) => {
let parsed = json_data.as_object().ok_or_else(|| {
SQLiteError(
SQLiteError::with_description(
ResultCode::CONSTRAINT_DATATYPE,
Some("expected oplog data to be an object".to_string()),
"expected oplog data to be an object",
)
})?;

Expand Down Expand Up @@ -571,9 +568,8 @@ impl<'a> PreparedPendingStatement<'a> {
self.stmt
.bind_text((i + 1) as i32, id, Destructor::STATIC)?;
} else {
return Err(SQLiteError(
ResultCode::MISUSE,
Some("Raw delete statement parameters must only reference id".to_string()),
return Err(SQLiteError::misuse(
"Raw delete statement parameters must only reference id",
));
}
}
Expand Down