Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bindings/c/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ name = "compat"
path = "tests/compat/mod.rs"

[build-dependencies]
cc = "1"
11 changes: 11 additions & 0 deletions bindings/c/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
fn main() {
println!("cargo:rustc-link-search=native=target/debug");
println!("cargo:rerun-if-changed=src/varargs.c");
// varargs.c exists because some sqlite3 entry points (db_config, and
// eventually mprintf/snprintf) are C-variadic, which stable Rust cannot
// define. Faking them with fixed args breaks on Apple arm64, where
// variadic arguments are passed on the stack but named arguments in
// registers — the callee would read garbage. The exported sqlite3_*
// symbols themselves are naked-function trampolines in lib.rs; see the
// comment there.
cc::Build::new()
.file("src/varargs.c")
.compile("turso_sqlite3_varargs");
}
69 changes: 67 additions & 2 deletions bindings/c/include/sqlite3.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#define SQLITE_CANTOPEN 14

#define SQLITE_CONSTRAINT 19

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like you just added these constants in sqlite3.h, but not in the Rust code? The way this works is that you define constants in lib.rs and the cbindgen the header.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved them into lib.rs as pub consts next to the existing result-code constants; the header mirrors them. (Values are verified against sqlite.h.in.)


#define SQLITE_MISUSE 21

#define SQLITE_ROW 100
Expand Down Expand Up @@ -87,7 +89,49 @@ void sqlite3_progress_handler(sqlite3 *_db, int _n, int (*_callback)(void *), vo

int sqlite3_busy_timeout(sqlite3 *_db, int _ms);

int sqlite3_set_authorizer(sqlite3 *_db, int (*_callback)(void), void *_context);
int sqlite3_set_authorizer(sqlite3 *db,
int (*xAuth)(void*, int, const char*, const char*, const char*, const char*),
void *pUserData);

/* Authorizer callback return codes */
#define SQLITE_DENY 1
#define SQLITE_IGNORE 2

/* Authorizer action codes (third parameter meanings per SQLite docs) */
#define SQLITE_CREATE_INDEX 1
#define SQLITE_CREATE_TABLE 2
#define SQLITE_CREATE_TEMP_INDEX 3
#define SQLITE_CREATE_TEMP_TABLE 4
#define SQLITE_CREATE_TEMP_TRIGGER 5
#define SQLITE_CREATE_TEMP_VIEW 6
#define SQLITE_CREATE_TRIGGER 7
#define SQLITE_CREATE_VIEW 8
#define SQLITE_DELETE 9
#define SQLITE_DROP_INDEX 10
#define SQLITE_DROP_TABLE 11
#define SQLITE_DROP_TEMP_INDEX 12
#define SQLITE_DROP_TEMP_TABLE 13
#define SQLITE_DROP_TEMP_TRIGGER 14
#define SQLITE_DROP_TEMP_VIEW 15
#define SQLITE_DROP_TRIGGER 16
#define SQLITE_DROP_VIEW 17
#define SQLITE_INSERT 18
#define SQLITE_PRAGMA 19
#define SQLITE_READ 20
#define SQLITE_SELECT 21
#define SQLITE_TRANSACTION 22
#define SQLITE_UPDATE 23
#define SQLITE_ATTACH 24
#define SQLITE_DETACH 25
#define SQLITE_ALTER_TABLE 26
#define SQLITE_REINDEX 27
#define SQLITE_ANALYZE 28
#define SQLITE_CREATE_VTABLE 29
#define SQLITE_DROP_VTABLE 30
#define SQLITE_FUNCTION 31
#define SQLITE_SAVEPOINT 32
#define SQLITE_COPY 0
#define SQLITE_RECURSIVE 33

void *sqlite3_context_db_handle(void *_context);

Expand Down Expand Up @@ -145,7 +189,28 @@ int64_t sqlite3_last_insert_rowid(sqlite3 *_db);

void sqlite3_interrupt(sqlite3 *_db);

int sqlite3_db_config(sqlite3 *_db, int _op);
int sqlite3_db_config(sqlite3 *db, int op, ...);

#define SQLITE_DBCONFIG_MAINDBNAME 1000
#define SQLITE_DBCONFIG_LOOKASIDE 1001
#define SQLITE_DBCONFIG_ENABLE_FKEY 1002
#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003
#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004
#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005
#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006
#define SQLITE_DBCONFIG_ENABLE_QPSG 1007
#define SQLITE_DBCONFIG_TRIGGER_EQP 1008
#define SQLITE_DBCONFIG_RESET_DATABASE 1009
#define SQLITE_DBCONFIG_DEFENSIVE 1010
#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011
#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012
#define SQLITE_DBCONFIG_DQS_DML 1013
#define SQLITE_DBCONFIG_DQS_DDL 1014
#define SQLITE_DBCONFIG_ENABLE_VIEW 1015
#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017

int sqlite3_extended_result_codes(sqlite3 *db, int onoff);

sqlite3 *sqlite3_db_handle(sqlite3_stmt *_stmt);

Expand Down
179 changes: 172 additions & 7 deletions bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,66 @@ pub const LIBSQL_STMTSTATUS_BASE: ffi::c_int = 1024;
pub const LIBSQL_STMTSTATUS_ROWS_READ: ffi::c_int = LIBSQL_STMTSTATUS_BASE + 1;
pub const LIBSQL_STMTSTATUS_ROWS_WRITTEN: ffi::c_int = LIBSQL_STMTSTATUS_BASE + 2;

/* authorizer callback return codes */
pub const SQLITE_DENY: ffi::c_int = 1;
pub const SQLITE_IGNORE: ffi::c_int = 2;

/* authorizer action codes */
pub const SQLITE_COPY: ffi::c_int = 0;
pub const SQLITE_CREATE_INDEX: ffi::c_int = 1;
pub const SQLITE_CREATE_TABLE: ffi::c_int = 2;
pub const SQLITE_CREATE_TEMP_INDEX: ffi::c_int = 3;
pub const SQLITE_CREATE_TEMP_TABLE: ffi::c_int = 4;
pub const SQLITE_CREATE_TEMP_TRIGGER: ffi::c_int = 5;
pub const SQLITE_CREATE_TEMP_VIEW: ffi::c_int = 6;
pub const SQLITE_CREATE_TRIGGER: ffi::c_int = 7;
pub const SQLITE_CREATE_VIEW: ffi::c_int = 8;
pub const SQLITE_DELETE: ffi::c_int = 9;
pub const SQLITE_DROP_INDEX: ffi::c_int = 10;
pub const SQLITE_DROP_TABLE: ffi::c_int = 11;
pub const SQLITE_DROP_TEMP_INDEX: ffi::c_int = 12;
pub const SQLITE_DROP_TEMP_TABLE: ffi::c_int = 13;
pub const SQLITE_DROP_TEMP_TRIGGER: ffi::c_int = 14;
pub const SQLITE_DROP_TEMP_VIEW: ffi::c_int = 15;
pub const SQLITE_DROP_TRIGGER: ffi::c_int = 16;
pub const SQLITE_DROP_VIEW: ffi::c_int = 17;
pub const SQLITE_INSERT: ffi::c_int = 18;
pub const SQLITE_PRAGMA: ffi::c_int = 19;
pub const SQLITE_READ: ffi::c_int = 20;
pub const SQLITE_SELECT: ffi::c_int = 21;
pub const SQLITE_TRANSACTION: ffi::c_int = 22;
pub const SQLITE_UPDATE: ffi::c_int = 23;
pub const SQLITE_ATTACH: ffi::c_int = 24;
pub const SQLITE_DETACH: ffi::c_int = 25;
pub const SQLITE_ALTER_TABLE: ffi::c_int = 26;
pub const SQLITE_REINDEX: ffi::c_int = 27;
pub const SQLITE_ANALYZE: ffi::c_int = 28;
pub const SQLITE_CREATE_VTABLE: ffi::c_int = 29;
pub const SQLITE_DROP_VTABLE: ffi::c_int = 30;
pub const SQLITE_FUNCTION: ffi::c_int = 31;
pub const SQLITE_SAVEPOINT: ffi::c_int = 32;
pub const SQLITE_RECURSIVE: ffi::c_int = 33;

/* sqlite3_db_config operations */
pub const SQLITE_DBCONFIG_MAINDBNAME: ffi::c_int = 1000;
pub const SQLITE_DBCONFIG_LOOKASIDE: ffi::c_int = 1001;
pub const SQLITE_DBCONFIG_ENABLE_FKEY: ffi::c_int = 1002;
pub const SQLITE_DBCONFIG_ENABLE_TRIGGER: ffi::c_int = 1003;
pub const SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: ffi::c_int = 1004;
pub const SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: ffi::c_int = 1005;
pub const SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: ffi::c_int = 1006;
pub const SQLITE_DBCONFIG_ENABLE_QPSG: ffi::c_int = 1007;
pub const SQLITE_DBCONFIG_TRIGGER_EQP: ffi::c_int = 1008;
pub const SQLITE_DBCONFIG_RESET_DATABASE: ffi::c_int = 1009;
pub const SQLITE_DBCONFIG_DEFENSIVE: ffi::c_int = 1010;
pub const SQLITE_DBCONFIG_WRITABLE_SCHEMA: ffi::c_int = 1011;
pub const SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: ffi::c_int = 1012;
pub const SQLITE_DBCONFIG_DQS_DML: ffi::c_int = 1013;
pub const SQLITE_DBCONFIG_DQS_DDL: ffi::c_int = 1014;
pub const SQLITE_DBCONFIG_ENABLE_VIEW: ffi::c_int = 1015;
pub const SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: ffi::c_int = 1016;
pub const SQLITE_DBCONFIG_TRUSTED_SCHEMA: ffi::c_int = 1017;

pub struct sqlite3 {
pub(crate) inner: Arc<Mutex<sqlite3Inner>>,
}
Expand Down Expand Up @@ -136,7 +196,9 @@ impl sqlite3 {
_db: db,
conn,
err_code: SQLITE_OK,
err_mask: 0xFFFFFFFFu32 as i32,
// Extended result codes are disabled by default, as in SQLite;
// sqlite3_extended_result_codes() widens the mask.
err_mask: 0xff,
malloc_failed: false,
e_open_state: SQLITE_STATE_OPEN,
p_err: std::ptr::null_mut(),
Expand Down Expand Up @@ -791,13 +853,33 @@ pub unsafe extern "C" fn sqlite3_busy_timeout(db: *mut sqlite3, ms: ffi::c_int)
SQLITE_OK
}

/// Callback type for sqlite3_set_authorizer: (userData, actionCode, arg1,
/// arg2, database, trigger-or-view) -> SQLITE_OK / SQLITE_DENY / SQLITE_IGNORE.
pub type sqlite3_authorizer_callback = unsafe extern "C" fn(
*mut ffi::c_void,
ffi::c_int,
*const ffi::c_char,
*const ffi::c_char,
*const ffi::c_char,
*const ffi::c_char,
) -> ffi::c_int;

/// Refused with SQLITE_ERROR: turso_core has no prepare-time authorization
/// hook, so a stored callback would never be invoked and callers (e.g. PHP,
/// which routes its open_basedir checks through the authorizer) would
/// silently lose the enforcement they registered for. PHP ignores this
/// return value when it installs its authorizer at open, so refusing does
/// not break it. Implement for real once core grows an authorization hook.
#[no_mangle]
pub unsafe extern "C" fn sqlite3_set_authorizer(
_db: *mut sqlite3,
_callback: Option<unsafe extern "C" fn() -> ffi::c_int>,
db: *mut sqlite3,
_callback: Option<sqlite3_authorizer_callback>,
_context: *mut ffi::c_void,
) -> ffi::c_int {
stub!();
if db.is_null() {
return SQLITE_MISUSE;
}
SQLITE_ERROR
}

#[no_mangle]
Expand Down Expand Up @@ -1403,9 +1485,66 @@ pub unsafe extern "C" fn sqlite3_interrupt(db: *mut sqlite3) {
inner.conn.interrupt();
}

extern "C" {
/// Variadic implementation in varargs.c; decodes the va_list and calls
/// turso_db_config_int below. Declared argument-less because it is only
/// referenced as a `sym` jump target, never called from Rust.
fn turso_sqlite3_db_config_va();
}

/// Exported trampoline for the variadic sqlite3_db_config. rustc exports
/// only Rust items from cdylibs (version script on ELF, exported-symbols
/// list on Apple), so the public symbol must be defined here; the tail jump
/// hands the untouched variadic call frame to the C implementation, whose
/// own prologue then performs va_start as for a direct call. This also
/// pins varargs.c's object into the link. Once rustc can export
/// native-library symbols from cdylibs (rust-lang/rust#155697), this
/// trampoline can be deleted and the C function renamed sqlite3_db_config.
#[unsafe(naked)]
#[no_mangle]
pub unsafe extern "C" fn sqlite3_db_config(_db: *mut sqlite3, _op: ffi::c_int) -> ffi::c_int {
stub!();
pub unsafe extern "C" fn sqlite3_db_config(_db: *mut ffi::c_void, _op: ffi::c_int) -> ffi::c_int {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
core::arch::naked_asm!("jmp {}", sym turso_sqlite3_db_config_va);
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
core::arch::naked_asm!("b {}", sym turso_sqlite3_db_config_va);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a weird way to do this. You can just drop sqlite3_db_config() from the Rust file altogether and use the symbol from the C code. Btw rust-lang/rust#155697 eventually solve this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the C symbol directly was the first version — CI rejected it with mold: error: undefined symbol: sqlite3_db_config: rustc only exports Rust items from cdylibs (version script on ELF, exported-symbols list on Apple), so a symbol defined in a linked C object never makes it into the dynamic symbol table. Until rust-lang/rust#155697 lands, the exported symbol has to be a Rust item, and a naked tail-jump is the only stable-Rust way to forward a variadic call frame. Added a comment on the trampoline saying it's deletable (and the C function renamable to sqlite3_db_config) once that PR lands.


/// Non-variadic backend for sqlite3_db_config. The variadic C entry point in
/// varargs.c decodes the (int value, int *pOut) argument shape shared by all
/// boolean DBCONFIG ops and forwards here. For boolean ops, a negative value
/// queries the current setting without changing it, and the resulting state
/// is written through `p_out` when non-NULL, as in SQLite.
#[no_mangle]
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe extern "C" fn turso_db_config_int(
db: *mut sqlite3,
op: ffi::c_int,
value: ffi::c_int,
p_out: *mut ffi::c_int,
) -> ffi::c_int {
if db.is_null() {
return SQLITE_MISUSE;
}
let _ = (value, p_out);
match op {
// Refused rather than accepted-and-ignored, so a caller that checks
// the return cannot be misled into believing it toggled anything.
// The protections DEFENSIVE stands for are unconditionally on in
// turso_core today:
// - sqlite_schema DML is rejected with no writable_schema unlock
// (core/schema.rs, allow_user_dml)
// - PRAGMA schema_version=N is a deliberate no-op
// (core/translate/pragma.rs, SchemaVersion write arm)
// - PRAGMA journal_mode=OFF is unsupported and silently kept as-is
// (core/storage/journal_mode.rs, JournalMode::supported)
// - there are no SQLite-style shadow tables
// Callers that need file-corruption protection therefore already
// have it; PHP's ext/sqlite3 ignores this return value. Switching to
// accept + enforce is safe only if every feature above gains a
// per-connection defensive gate in core first.
SQLITE_DBCONFIG_DEFENSIVE => SQLITE_ERROR,
_ => SQLITE_ERROR,
}
}

#[no_mangle]
Expand Down Expand Up @@ -1468,6 +1607,30 @@ pub unsafe extern "C" fn sqlite3_errcode(db: *mut sqlite3) -> ffi::c_int {
db.err_code & db.err_mask
}

/// Enables or disables extended result codes for the connection. When
/// disabled (the default), sqlite3_errcode and statement results report only
/// the primary result code; sqlite3_extended_errcode always reports the
/// extended code either way.
#[no_mangle]
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe extern "C" fn sqlite3_extended_result_codes(
db: *mut sqlite3,
onoff: ffi::c_int,
) -> ffi::c_int {
if db.is_null() {
return SQLITE_MISUSE;
}
// SAFETY: db checked non-null just above; caller guarantees it points to a
// live sqlite3 for the duration of the call.
let db: &sqlite3 = unsafe { &*db };
let mut inner = match db.inner.lock() {
Ok(guard) => guard,
Err(_) => return SQLITE_MISUSE,
};
inner.err_mask = if onoff != 0 { -1 } else { 0xff };
SQLITE_OK
}

#[no_mangle]
pub unsafe extern "C" fn sqlite3_errstr(_err: ffi::c_int) -> *const ffi::c_char {
sqlite3_errstr_impl(_err)
Expand Down Expand Up @@ -2758,7 +2921,9 @@ pub unsafe extern "C" fn sqlite3_extended_errcode(db: *mut sqlite3) -> ffi::c_in
if db.malloc_failed {
return SQLITE_NOMEM;
}
db.err_code & db.err_mask
// The extended code is reported regardless of the
// sqlite3_extended_result_codes() setting; only sqlite3_errcode masks.
db.err_code
}

#[no_mangle]
Expand Down
49 changes: 49 additions & 0 deletions bindings/c/src/varargs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
** Variadic C entry points that cannot be written in stable Rust (C variadics
** are a nightly-only Rust feature, and on some ABIs — e.g. Apple arm64 —
** variadic arguments are passed differently from named arguments, so the
** entry point must genuinely be variadic).
**
** Each function here decodes its va_list and forwards to a non-variadic
** `turso_*` function exported from the Rust side of the crate. The public
** sqlite3_* symbol itself is a naked-function trampoline in lib.rs that
** tail-jumps here: rustc only exports Rust items from cdylibs, so the
** exported symbol must be a Rust item, and a tail jump preserves the
** variadic call frame intact.
*/
#include <stdarg.h>
#include <stddef.h>

typedef struct sqlite3 sqlite3;

#define SQLITE_ERROR 1

extern int turso_db_config_int(sqlite3 *db, int op, int value, int *p_out);

/*
** sqlite3_db_config() argument shapes, by op:
** SQLITE_DBCONFIG_MAINDBNAME (1000) takes (const char*)
** SQLITE_DBCONFIG_LOOKASIDE (1001) takes (void*, int, int)
** the boolean ops (1002..1017) take (int, int*)
** The va_list is decoded only for ops known to use the (int, int*) shape;
** any other op — including ops newer than this header — is rejected without
** reading the va_list, since decoding an unknown shape is undefined
** behavior when the caller passed something else.
*/
#define DBCONFIG_BOOL_FIRST 1002 /* SQLITE_DBCONFIG_ENABLE_FKEY */
#define DBCONFIG_BOOL_LAST 1017 /* SQLITE_DBCONFIG_TRUSTED_SCHEMA */

int turso_sqlite3_db_config_va(sqlite3 *db, int op, ...) {
va_list ap;
int value;
int *p_out;

if (op < DBCONFIG_BOOL_FIRST || op > DBCONFIG_BOOL_LAST) {
return SQLITE_ERROR;
}
va_start(ap, op);
value = va_arg(ap, int);
p_out = va_arg(ap, int *);
va_end(ap);
return turso_db_config_int(db, op, value, p_out);
}
Loading
Loading