Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bc91ca3
github: Disallow unwrap() in code with Clippy
penberg Nov 20, 2025
b6b86bb
Add section on error handlng to CONTRIBUTING.md
penberg Nov 21, 2025
1f35a25
parser: Allow unwrap() in the code
penberg Nov 20, 2025
3b32a46
core: Replace unwrap() calls with expect() in build.rs
penberg Nov 20, 2025
79e6199
core: Improve error handling in schema.rs
penberg Nov 20, 2025
404effa
cli: Replace unwrap() calls with expect() in build.rs
penberg Nov 20, 2025
e3a0060
core/ext: Switch to parking_lot::Mutex
penberg Nov 20, 2025
3b281b8
core/ext: Replace unwrap() with expect() in vtab_xconnect.rs
penberg Nov 20, 2025
5834649
core/functions: Improve error handling in datetime.rs
penberg Nov 20, 2025
1ca0ab3
core/translate: Replace unwrap() calls with expect() in plan.rs
penberg Nov 20, 2025
4051e42
core/translate: Eliminate unwrap() calls in select.rs
penberg Nov 20, 2025
93a29f3
core/translate: Improve error handling in emitter.rs
penberg Nov 20, 2025
c66989f
core/translate: Improve error handling in expr.rs
penberg Nov 20, 2025
bb66df3
core/translate: Improve error handling in main_loop.rs
penberg Nov 20, 2025
91e25ae
core/translate: Improve error handling in insert.rs
penberg Nov 20, 2025
0fd64a7
core/translate: Improve error handling in constraints.rs
penberg Nov 20, 2025
78b11b6
core/translate: Improve error handling in planner.rs
penberg Nov 20, 2025
e3ae3c0
core/translate: Improve error handling in index.rs
penberg Nov 20, 2025
246c990
core/translate: Improve error handling in schema.rs
penberg Nov 20, 2025
bebc866
core/translate: Improve error handling in optimizer/mod.rs
penberg Nov 20, 2025
ff22e31
core/translate: Improve error handling in display.rs
penberg Nov 20, 2025
4cd7ff1
core/storage: Improve error handling in btree.rs
penberg Nov 20, 2025
feed039
core/storage: Improve error handling in sqlite3_ondisk.rs
penberg Nov 21, 2025
9dead4f
core/storage: Eliminate unwrap() from encryption.rs
penberg Nov 21, 2025
6b210b5
core/storage: Improve error handling in wal.rs
penberg Nov 21, 2025
636cc35
core/storage: Improve error handling in pager.rs
penberg Nov 21, 2025
f1692c6
core/storage: Improve error handling in checksum.rs
penberg Nov 21, 2025
e907a85
core/storage: Improve error handling in page_cache.rs
penberg Nov 21, 2025
9cc4cb0
core/storage: Improve error handling in database.rs
penberg Nov 21, 2025
6133452
core/mvcc: Improve error handling in database/mod.rs
penberg Nov 23, 2025
1028d67
core/mvcc: Improve error handling in logical_log.rs
penberg Nov 23, 2025
7e55314
core/mvcc: Improve error handling in checkpoint_state_machine.rs
penberg Nov 23, 2025
91a2c3c
core/mvcc: Improve error handling in cursor.rs
penberg Nov 23, 2025
0b68a1b
core/io: Improve error handling
penberg Nov 23, 2025
5d662e7
core/vector: Eliminate unwrap() calls from mod.rs
penberg Nov 24, 2025
dceedc2
core/vector: Improve error handling in vector_types.rs
penberg Nov 24, 2025
ca15a3f
core/time: Improve error handling
penberg Nov 24, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
- uses: actions/checkout@v3
- name: Clippy
run: |
cargo clippy --workspace --all-features --all-targets -- --deny=warnings
cargo clippy --workspace --all-features --lib --bins -- --deny=warnings -D clippy::unwrap_used

simulator:
runs-on: blacksmith-4vcpu-ubuntu-2404
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
cargo bench --bench benchmark -- --profile-time=5
```

## Coding Style

### Error handling

* Turso is a library, which means we prefer error handling over crashing.
* However, we care about correctness more than crashing, which means we use assertions liberally.
* We don't use `unwrap()` in Turso code, except for tests. Instead, we implement error handling or -- when not possible -- use expect() to clearly communicate that this is an invariant (assertion).

## Debugging bugs

### Query execution debugging
Expand Down
8 changes: 4 additions & 4 deletions cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ fn main() {
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-changed=manuals");

let out_dir = env::var_os("OUT_DIR").unwrap();
let syntax =
SyntaxDefinition::load_from_str(include_str!("./SQL.sublime-syntax"), false, None).unwrap();
let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR not set by cargo");
let syntax = SyntaxDefinition::load_from_str(include_str!("./SQL.sublime-syntax"), false, None)
.expect("failed to load SQL syntax definition");
let mut ps = SyntaxSet::new().into_builder();
ps.add(syntax);
let ps = ps.build();
dump_to_uncompressed_file(
&ps,
Path::new(&out_dir).join("SQL_syntax_set_dump.packdump"),
)
.unwrap();
.expect("failed to dump syntax set");
}
5 changes: 3 additions & 2 deletions core/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
println!("cargo::rerun-if-changed=build.rs");
}

let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set by cargo"));
let built_file = out_dir.join("built.rs");

built::write_built_file().expect("Failed to acquire build-time information");
Expand All @@ -19,7 +19,8 @@ fn main() {
&built_file,
format!(
"{}\npub const BUILT_TIME_SQLITE: &str = \"{}\";\n",
fs::read_to_string(&built_file).unwrap(),
fs::read_to_string(&built_file)
.expect("built.rs should exist after built::write_built_file()"),
sqlite_date
),
)
Expand Down
17 changes: 4 additions & 13 deletions core/ext/dynamic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use crate::{
};
#[cfg(not(target_family = "wasm"))]
use libloading::{Library, Symbol};
use parking_lot::Mutex;
use std::{
ffi::{c_char, CString},
sync::{Arc, Mutex, OnceLock},
sync::{Arc, OnceLock},
};
use turso_ext::{ExtensionApi, ExtensionApiRef, ExtensionEntryPoint, ResultCode, VfsImpl};

Expand Down Expand Up @@ -52,12 +53,7 @@ impl Connection {
let result_code = unsafe { entry(api_ptr) };
if result_code.is_ok() {
let extensions = get_extension_libraries();
extensions
.lock()
.map_err(|_| {
LimboError::ExtensionError("Error locking extension libraries".to_string())
})?
.push((Arc::new(lib), api_ref));
extensions.lock().push((Arc::new(lib), api_ref));
if self.is_db_initialized() {
self.parse_schema_rows()?;
}
Expand Down Expand Up @@ -156,10 +152,7 @@ fn register_static_vfs_modules(_api: &mut ExtensionApi) {
}

pub fn add_vfs_module(name: String, vfs: Arc<VfsMod>) {
let mut modules = VFS_MODULES
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap();
let mut modules = VFS_MODULES.get_or_init(|| Mutex::new(Vec::new())).lock();
if !modules.iter().any(|v| v.0 == name) {
modules.push((name, vfs));
}
Expand All @@ -169,7 +162,6 @@ pub fn list_vfs_modules() -> Vec<String> {
VFS_MODULES
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.iter()
.map(|v| v.0.clone())
.collect()
Expand All @@ -179,6 +171,5 @@ pub fn get_vfs_modules() -> Vec<Vfs> {
VFS_MODULES
.get_or_init(|| Mutex::new(Vec::new()))
.lock()
.unwrap()
.clone()
}
2 changes: 1 addition & 1 deletion core/ext/vtab_xconnect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub unsafe extern "C" fn execute(
let args_slice = &mut std::slice::from_raw_parts_mut(args, arg_count as usize);
for (i, val) in args_slice.iter_mut().enumerate() {
stmt.bind_at(
NonZeroUsize::new(i + 1).unwrap(),
NonZeroUsize::new(i + 1).expect("i cannot be negative"),
Value::from_ffi(std::mem::take(val)).unwrap_or(Value::Null),
);
}
Expand Down
58 changes: 39 additions & 19 deletions core/functions/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ where
return Value::Null;
}

let value = values.next().unwrap();
let Some(value) = values.next() else {
return Value::Null;
};
let value = value.as_value_ref();
let format_str = if matches!(
value,
Expand Down Expand Up @@ -80,11 +82,15 @@ where
{
let values = values.into_iter();
if values.len() == 0 {
let now = parse_naive_date_time(Value::build_text("now")).unwrap();
let Some(now) = parse_naive_date_time(Value::build_text("now")) else {
return Value::Null;
};
return format_dt(now, output_type, false);
}
let mut values = values.peekable();
let first = values.peek().unwrap();
let Some(first) = values.peek() else {
return Value::Null;
};
if let Some(mut dt) = parse_naive_date_time(first) {
// if successful, treat subsequent entries as modifiers
modify_dt(&mut dt, values.skip(1), output_type)
Expand Down Expand Up @@ -231,18 +237,19 @@ fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str, n_floor: &mut i64) ->
}
Modifier::StartOfMonth => {
*dt = NaiveDate::from_ymd_opt(dt.year(), dt.month(), 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
.and_then(|d| d.and_hms_opt(0, 0, 0))
.ok_or_else(|| InvalidModifier("failed to construct start of month".to_string()))?;
}
Modifier::StartOfYear => {
*dt = NaiveDate::from_ymd_opt(dt.year(), 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
.and_then(|d| d.and_hms_opt(0, 0, 0))
.ok_or_else(|| InvalidModifier("failed to construct start of year".to_string()))?;
}
Modifier::StartOfDay => {
*dt = dt.date().and_hms_opt(0, 0, 0).unwrap();
*dt = dt
.date()
.and_hms_opt(0, 0, 0)
.ok_or_else(|| InvalidModifier("failed to construct start of day".to_string()))?;
}
Modifier::Weekday(day) => {
let current_day = dt.weekday().num_days_from_sunday();
Expand All @@ -259,11 +266,18 @@ fn apply_modifier(dt: &mut NaiveDateTime, modifier: &str, n_floor: &mut i64) ->
}
Modifier::Utc => {
// TODO: handle datetime('now', 'utc') no-op
let local_dt = chrono::Local.from_local_datetime(dt).unwrap();
let local_dt = chrono::Local
.from_local_datetime(dt)
.single()
.ok_or_else(|| {
InvalidModifier("ambiguous local datetime during DST transition".to_string())
})?;
*dt = local_dt.with_timezone(&Utc).naive_utc();
}
Modifier::Subsec => {
*dt = dt.with_nanosecond(dt.nanosecond()).unwrap();
*dt = dt
.with_nanosecond(dt.nanosecond())
.ok_or_else(|| InvalidModifier("failed to set nanoseconds".to_string()))?;
return Ok(true);
}
}
Expand Down Expand Up @@ -482,7 +496,7 @@ fn get_date_time_from_time_value_string(value: &str) -> Option<NaiveDateTime> {

// First, try to parse as date-only format
if let Ok(date) = NaiveDate::parse_from_str(value, date_only_format) {
return Some(date.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap()));
return NaiveTime::from_hms_opt(0, 0, 0).map(|time| date.and_time(time));
}

for format in &datetime_formats {
Expand Down Expand Up @@ -598,10 +612,9 @@ fn is_leap_second(dt: &NaiveDateTime) -> bool {

fn get_max_datetime_exclusive() -> NaiveDateTime {
// The maximum date in SQLite is 9999-12-31
NaiveDateTime::new(
NaiveDate::from_ymd_opt(10000, 1, 1).unwrap(),
NaiveTime::from_hms_milli_opt(00, 00, 00, 000).unwrap(),
)
let date = NaiveDate::from_ymd_opt(10000, 1, 1).expect("10000-01-01 is valid");
let time = NaiveTime::from_hms_milli_opt(00, 00, 00, 000).expect("00:00:00.000 is valid");
NaiveDateTime::new(date, time)
}

/// Modifier doc https://www.sqlite.org/lang_datefunc.html#modifiers
Expand Down Expand Up @@ -824,8 +837,15 @@ where
return Value::Null;
}

let start = parse_naive_date_time(values.next().unwrap());
let end = parse_naive_date_time(values.next().unwrap());
let Some(start_val) = values.next() else {
return Value::Null;
};
let Some(end_val) = values.next() else {
return Value::Null;
};

let start = parse_naive_date_time(start_val);
let end = parse_naive_date_time(end_val);

match (start, end) {
(Some(start), Some(end)) => {
Expand Down
6 changes: 4 additions & 2 deletions core/io/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,17 @@ impl std::ops::Add<Duration> for Instant {
type Output = Instant;

fn add(self, rhs: Duration) -> Self::Output {
self.checked_add_duration(&rhs).unwrap()
self.checked_add_duration(&rhs)
.expect("duration addition overflow")
}
}

impl std::ops::Sub<Duration> for Instant {
type Output = Instant;

fn sub(self, rhs: Duration) -> Self::Output {
self.checked_sub_duration(&rhs).unwrap()
self.checked_sub_duration(&rhs)
.expect("duration subtraction underflow")
}
}

Expand Down
4 changes: 3 additions & 1 deletion core/io/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ impl Completion {
}

pub(super) fn get_inner(&self) -> &Arc<CompletionInner> {
self.inner.as_ref().unwrap()
self.inner
.as_ref()
.expect("completion inner should be initialized")
}

pub fn needs_link(&self) -> bool {
Expand Down
7 changes: 6 additions & 1 deletion core/io/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ impl IO for MemoryIO {
}),
);
}
Ok(files.get(path).unwrap().clone())
Ok(files
.get(path)
.ok_or(crate::LimboError::InternalError(
"file should exist after insert".to_string(),
))?
.clone())
}
fn remove_file(&self, path: &str) -> Result<()> {
let mut files = self.files.lock();
Expand Down
28 changes: 22 additions & 6 deletions core/mvcc/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::mvcc::database::{MVTableId, MvStore, Row, RowID, RowKey, RowVersionSt
use crate::storage::btree::{BTreeCursor, BTreeKey, CursorTrait};
use crate::translate::plan::IterationDirection;
use crate::types::{IOResult, ImmutableRecord, RecordCursor, SeekKey, SeekOp, SeekResult};
use crate::{return_if_io, Result};
use crate::{return_if_io, LimboError, Result};
use crate::{Pager, Value};
use std::any::Any;
use std::cell::{Ref, RefCell};
Expand Down Expand Up @@ -110,7 +110,9 @@ impl<Clock: LogicalClock + 'static> MvccLazyCursor<Clock> {
};
{
let mut record = self.get_immutable_record_or_create();
let record = record.as_mut().unwrap();
let record = record.as_mut().ok_or(LimboError::InternalError(
"immutable record not initialized".to_string(),
))?;
record.invalidate();
record.start_serialization(&row.data);
}
Expand All @@ -119,7 +121,10 @@ impl<Clock: LogicalClock + 'static> MvccLazyCursor<Clock> {
Ref::filter_map(self.reusable_immutable_record.borrow(), |opt| {
opt.as_ref()
})
.unwrap();
.ok()
.ok_or(LimboError::InternalError(
"immutable record not initialized".to_string(),
))?;
Ok(IOResult::Done(Some(record_ref)))
}
}
Expand Down Expand Up @@ -587,10 +592,21 @@ impl<Clock: LogicalClock + 'static> CursorTrait for MvccLazyCursor<Clock> {
panic!("BTreeKey::maybe_rowid() should return Some(rowid) for table rowid keys");
};
let row_id = RowID::new(self.table_id, RowKey::Int(rowid));
let record_buf = key.get_record().unwrap().get_payload().to_vec();
let record_buf = key
.get_record()
.ok_or(LimboError::InternalError(
"BTreeKey should have a record".to_string(),
))?
.get_payload()
.to_vec();
let num_columns = match key {
BTreeKey::IndexKey(record) => record.column_count(),
BTreeKey::TableRowId((_, record)) => record.as_ref().unwrap().column_count(),
BTreeKey::TableRowId((_, record)) => record
.as_ref()
.ok_or(LimboError::InternalError(
"TableRowId should have a record".to_string(),
))?
.column_count(),
};
let row = crate::mvcc::database::Row::new(row_id, record_buf, num_columns);

Expand Down Expand Up @@ -770,7 +786,7 @@ impl<Clock: LogicalClock + 'static> CursorTrait for MvccLazyCursor<Clock> {
fn invalidate_record(&mut self) {
self.get_immutable_record_or_create()
.as_mut()
.unwrap()
.expect("immutable record should be initialized")
.invalidate();
self.record_cursor.borrow_mut().invalidate();
}
Expand Down
Loading
Loading