Merge 'SQLite C API improvements' from Nikita Sivukhin
Some checks are pending
Build and push limbo-sim image / deploy (push) Waiting to run
C compat Tests / test (push) Waiting to run
Dart/Flutter / test (blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Dart/Flutter / test (windows-latest) (push) Waiting to run
Dart/Flutter / precompile (blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Dart/Flutter / precompile (macOS-latest) (push) Waiting to run
Dart/Flutter / precompile (windows-latest) (push) Waiting to run
Dart/Flutter / publish (push) Waiting to run
Run long fuzz tests and stress test / run-fuzz-tests (push) Waiting to run
Run long fuzz tests and stress test / run-long-fuzz-tests (push) Waiting to run
Run long fuzz tests and stress test / simple-stress-test (push) Waiting to run
Java Tests / test (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-aarch64-apple-darwin - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-aarch64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-wasm32-wasip1-threads - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-x86_64-pc-windows-msvc - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / db-bindings-x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-wasm32-wasip1-threads - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-aarch64-apple-darwin - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-aarch64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-x86_64-pc-windows-msvc - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / sync-bindings-x86_64-unknown-linux-gnu - node@20 (push) Waiting to run
Build & publish @tursodatabase/database / Test DB bindings on Linux-x64-gnu - node@20 (push) Blocked by required conditions
Build & publish @tursodatabase/database / Test DB bindings on browser@20 (push) Blocked by required conditions
Build & publish @tursodatabase/database / Publish (push) Blocked by required conditions
Python / sdist (push) Waiting to run
Python / Release (push) Blocked by required conditions
Python / configure-strategy (push) Waiting to run
Python / test (push) Blocked by required conditions
Python / lint (push) Waiting to run
Python / linux (x86_64) (push) Waiting to run
Python / macos-arm64 (aarch64) (push) Waiting to run
Rust / cargo-fmt-check (push) Waiting to run
Rust / build-native (blacksmith-4vcpu-ubuntu-2404) (push) Waiting to run
Rust / build-native (macos-latest) (push) Waiting to run
Rust / build-native (windows-latest) (push) Waiting to run
Rust / clippy (push) Waiting to run
Rust / simulator (push) Waiting to run
Rust / test-limbo (push) Waiting to run
Rust / test-sqlite (push) Waiting to run
Rust Benchmarks+Nyrkiö / bench (push) Waiting to run
Rust Benchmarks+Nyrkiö / clickbench (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h-criterion (push) Waiting to run
Rust Benchmarks+Nyrkiö / tpc-h (push) Waiting to run
Rust Benchmarks+Nyrkiö / vfs-bench-compile (push) Waiting to run

This PR adds rich error codes for sqlite3 C bridge layer and also do few
minor adjustments:
1. Enable indices by default
2. Return debug logs to the stderr

Closes #3999
This commit is contained in:
Pekka Enberg 2025-11-21 13:20:44 +02:00 committed by GitHub
commit d2fc52834a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -14,19 +14,42 @@ macro_rules! stub {
};
}
pub const SQLITE_OK: ffi::c_int = 0;
pub const SQLITE_ERROR: ffi::c_int = 1;
pub const SQLITE_ABORT: ffi::c_int = 4;
pub const SQLITE_BUSY: ffi::c_int = 5;
pub const SQLITE_NOMEM: ffi::c_int = 7;
pub const SQLITE_INTERRUPT: ffi::c_int = 9;
pub const SQLITE_NOTFOUND: ffi::c_int = 12;
pub const SQLITE_CANTOPEN: ffi::c_int = 14;
pub const SQLITE_MISUSE: ffi::c_int = 21;
pub const SQLITE_RANGE: ffi::c_int = 25;
pub const SQLITE_ROW: ffi::c_int = 100;
pub const SQLITE_DONE: ffi::c_int = 101;
/* generic error-codes */
pub const SQLITE_OK: ffi::c_int = 0; /* Successful result */
pub const SQLITE_ERROR: ffi::c_int = 1; /* Generic error */
pub const SQLITE_INTERNAL: ffi::c_int = 2; /* Internal logic error in SQLite */
pub const SQLITE_PERM: ffi::c_int = 3; /* Access permission denied */
pub const SQLITE_ABORT: ffi::c_int = 4; /* Callback routine requested an abort */
pub const SQLITE_BUSY: ffi::c_int = 5; /* The database file is locked */
pub const SQLITE_LOCKED: ffi::c_int = 6; /* A table in the database is locked */
pub const SQLITE_NOMEM: ffi::c_int = 7; /* A malloc() failed */
pub const SQLITE_READONLY: ffi::c_int = 8; /* Attempt to write a readonly database */
pub const SQLITE_INTERRUPT: ffi::c_int = 9; /* Operation terminated by sqlite3_interrupt()*/
pub const SQLITE_IOERR: ffi::c_int = 10; /* Some kind of disk I/O error occurred */
pub const SQLITE_CORRUPT: ffi::c_int = 11; /* The database disk image is malformed */
pub const SQLITE_NOTFOUND: ffi::c_int = 12; /* Unknown opcode in sqlite3_file_control() */
pub const SQLITE_FULL: ffi::c_int = 13; /* Insertion failed because database is full */
pub const SQLITE_CANTOPEN: ffi::c_int = 14; /* Unable to open the database file */
pub const SQLITE_PROTOCOL: ffi::c_int = 15; /* Database lock protocol error */
pub const SQLITE_EMPTY: ffi::c_int = 16; /* Internal use only */
pub const SQLITE_SCHEMA: ffi::c_int = 17; /* The database schema changed */
pub const SQLITE_TOOBIG: ffi::c_int = 18; /* String or BLOB exceeds size limit */
pub const SQLITE_CONSTRAINT: ffi::c_int = 19; /* Abort due to constraint violation */
pub const SQLITE_MISMATCH: ffi::c_int = 20; /* Data type mismatch */
pub const SQLITE_MISUSE: ffi::c_int = 21; /* Library used incorrectly */
pub const SQLITE_NOLFS: ffi::c_int = 22; /* Uses OS features not supported on host */
pub const SQLITE_AUTH: ffi::c_int = 23; /* Authorization denied */
pub const SQLITE_FORMAT: ffi::c_int = 24; /* Not used */
pub const SQLITE_RANGE: ffi::c_int = 25; /* 2nd parameter to sqlite3_bind out of range */
pub const SQLITE_NOTADB: ffi::c_int = 26; /* File opened that is not a database file */
pub const SQLITE_NOTICE: ffi::c_int = 27; /* Notifications from sqlite3_log() */
pub const SQLITE_WARNING: ffi::c_int = 28; /* Warnings from sqlite3_log() */
pub const SQLITE_ROW: ffi::c_int = 100; /* sqlite3_step() has another row ready */
pub const SQLITE_DONE: ffi::c_int = 101; /* sqlite3_step() has finished executing */
/* extended error-codes */
pub const SQLITE_ABORT_ROLLBACK: ffi::c_int = SQLITE_ABORT | (2 << 8);
pub const SQLITE_STATE_OPEN: u8 = 0x76;
pub const SQLITE_STATE_SICK: u8 = 0xba;
pub const SQLITE_STATE_BUSY: u8 = 0x6d;
@ -39,7 +62,6 @@ pub const SQLITE_CHECKPOINT_TRUNCATE: ffi::c_int = 3;
pub const SQLITE_INTEGER: ffi::c_int = 1;
pub const SQLITE_FLOAT: ffi::c_int = 2;
pub const SQLITE_TEXT: ffi::c_int = 3;
pub const SQLITE3_TEXT: ffi::c_int = 3;
pub const SQLITE_BLOB: ffi::c_int = 4;
pub const SQLITE_NULL: ffi::c_int = 5;
@ -161,7 +183,7 @@ pub unsafe extern "C" fn sqlite3_open(
Err(_) => return SQLITE_CANTOPEN,
},
};
match turso_core::Database::open_file(io.clone(), filename_str, false, false) {
match turso_core::Database::open_file(io.clone(), filename_str, false, true) {
Ok(db) => {
let conn = db.connect().unwrap();
let filename = match filename_str {
@ -288,9 +310,10 @@ pub unsafe extern "C" fn sqlite3_prepare_v2(
};
let stmt = match db.conn.prepare(sql) {
Ok(stmt) => stmt,
Err(_) => {
db.err_code = SQLITE_ERROR;
return SQLITE_ERROR;
Err(err) => {
let code = handle_limbo_err(err, std::ptr::null_mut());
db.err_code = code;
return code;
}
};
let new_stmt = Box::leak(Box::new(sqlite3_stmt::new(raw_db, stmt)));
@ -344,8 +367,8 @@ pub unsafe extern "C" fn sqlite3_step(stmt: *mut sqlite3_stmt) -> ffi::c_int {
let db = &mut *stmt.db;
loop {
let _db = db.inner.lock().unwrap();
if let Ok(result) = stmt.stmt.step() {
match result {
match stmt.stmt.step() {
Ok(result) => match result {
turso_core::StepResult::IO => {
stmt.stmt.run_once().unwrap();
continue;
@ -360,9 +383,8 @@ pub unsafe extern "C" fn sqlite3_step(stmt: *mut sqlite3_stmt) -> ffi::c_int {
return SQLITE_ROW;
}
turso_core::StepResult::Busy => return SQLITE_BUSY,
}
} else {
return SQLITE_ERROR;
},
Err(err) => return handle_limbo_err(err, std::ptr::null_mut()),
}
}
}
@ -412,11 +434,7 @@ pub unsafe extern "C" fn sqlite3_exec(
match db_inner.conn.execute(trimmed) {
Ok(_) => continue,
Err(e) => {
if !err.is_null() {
let err_msg = format!("SQL error: {e:?}");
*err = CString::new(err_msg).unwrap().into_raw();
}
return SQLITE_ERROR;
return handle_limbo_err(e, err);
}
}
} else if callback.is_none() {
@ -964,7 +982,6 @@ pub unsafe extern "C" fn sqlite3_bind_double(
idx: ffi::c_int,
val: f64,
) -> ffi::c_int {
println!("Bind Double Rust");
if stmt.is_null() {
return SQLITE_MISUSE;
}
@ -1801,7 +1818,6 @@ pub unsafe extern "C" fn sqlite3_wal_checkpoint_v2(
SQLITE_OK
}
Err(e) => {
println!("Checkpoint error: {e}");
if matches!(e, turso_core::LimboError::Busy) {
SQLITE_BUSY
} else {
@ -1946,13 +1962,10 @@ pub unsafe extern "C" fn libsql_wal_disable_checkpoint(db: *mut sqlite3) -> ffi:
}
fn sqlite3_safety_check_sick_or_ok(db: &sqlite3Inner) -> bool {
match db.e_open_state {
SQLITE_STATE_SICK | SQLITE_STATE_OPEN | SQLITE_STATE_BUSY => true,
_ => {
eprintln!("Invalid database state: {}", db.e_open_state);
false
}
}
matches!(
db.e_open_state,
SQLITE_STATE_SICK | SQLITE_STATE_OPEN | SQLITE_STATE_BUSY
)
}
// https://sqlite.org/c3ref/table_column_metadata.html
@ -2096,3 +2109,20 @@ pub unsafe extern "C" fn sqlite3_table_column_metadata(
rc
}
fn handle_limbo_err(err: LimboError, container: *mut *mut ffi::c_char) -> i32 {
if !container.is_null() {
let err_msg = format!("{err}");
unsafe { *container = CString::new(err_msg).unwrap().into_raw() };
}
match err {
LimboError::Corrupt(..) => SQLITE_CORRUPT,
LimboError::NotADB => SQLITE_NOTADB,
LimboError::Constraint(_) => SQLITE_CONSTRAINT,
LimboError::DatabaseFull(_) => SQLITE_FULL,
LimboError::TableLocked => SQLITE_LOCKED,
LimboError::ReadOnly => SQLITE_READONLY,
LimboError::Busy => SQLITE_BUSY,
_ => SQLITE_ERROR,
}
}