diff --git a/Cargo.lock b/Cargo.lock index f02ee0bcda0..26013747499 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7614,6 +7614,7 @@ dependencies = [ name = "turso_sqlite3" version = "0.8.0-pre.2" dependencies = [ + "cc", "env_logger", "libc", "tempfile", diff --git a/bindings/c/Cargo.toml b/bindings/c/Cargo.toml index bddfe737f00..378f04a6658 100644 --- a/bindings/c/Cargo.toml +++ b/bindings/c/Cargo.toml @@ -41,3 +41,4 @@ name = "compat" path = "tests/compat/mod.rs" [build-dependencies] +cc = "1" diff --git a/bindings/c/build.rs b/bindings/c/build.rs index c00f4d67979..9a7cd31ffa9 100644 --- a/bindings/c/build.rs +++ b/bindings/c/build.rs @@ -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"); } diff --git a/bindings/c/include/sqlite3.h b/bindings/c/include/sqlite3.h index 05a5db88ea1..bd19c9999c0 100644 --- a/bindings/c/include/sqlite3.h +++ b/bindings/c/include/sqlite3.h @@ -19,6 +19,8 @@ #define SQLITE_CANTOPEN 14 +#define SQLITE_CONSTRAINT 19 + #define SQLITE_MISUSE 21 #define SQLITE_ROW 100 @@ -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); @@ -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); diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index a0ed9806d32..7b52d5a45ce 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -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>, } @@ -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(), @@ -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 ffi::c_int>, + db: *mut sqlite3, + _callback: Option, _context: *mut ffi::c_void, ) -> ffi::c_int { - stub!(); + if db.is_null() { + return SQLITE_MISUSE; + } + SQLITE_ERROR } #[no_mangle] @@ -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); +} + +/// 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] @@ -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) @@ -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] diff --git a/bindings/c/src/varargs.c b/bindings/c/src/varargs.c new file mode 100644 index 00000000000..fb44f5e87da --- /dev/null +++ b/bindings/c/src/varargs.c @@ -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 +#include + +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); +} diff --git a/bindings/c/tests/sqlite3_tests.c b/bindings/c/tests/sqlite3_tests.c index e22a8005f85..4b1bb2b4be6 100644 --- a/bindings/c/tests/sqlite3_tests.c +++ b/bindings/c/tests/sqlite3_tests.c @@ -21,6 +21,9 @@ void test_sqlite3_column_decltype(); void test_sqlite3_next_stmt(); void test_sqlite3_table_column_metadata(); void test_sqlite3_insert_returning(); +void test_sqlite3_set_authorizer(); +void test_sqlite3_db_config(); +void test_sqlite3_extended_result_codes(); int allocated = 0; @@ -41,6 +44,9 @@ int main(void) test_sqlite3_next_stmt(); test_sqlite3_table_column_metadata(); test_sqlite3_insert_returning(); + test_sqlite3_set_authorizer(); + test_sqlite3_db_config(); + test_sqlite3_extended_result_codes(); return 0; } @@ -791,4 +797,174 @@ void test_sqlite3_insert_returning() sqlite3_close(db); printf("test_sqlite3_insert_retuning test passed\n"); -} \ No newline at end of file +} +/* +** Authorizer registration contract. SQLite accepts registrations: one +** authorizer per connection, each call replaces the previous one, NULL +** clears it. Turso refuses with SQLITE_ERROR (no prepare-time +** authorization hook exists, and a stored-but-never-invoked callback would +** silently drop the enforcement callers registered for). Either way, +** statement execution must be unaffected. Enforcement cases (DENY/IGNORE) +** arrive together with the core authorization hook. +*/ +static int authorizer_allow(void *user_data, int action, + const char *arg1, const char *arg2, + const char *database, const char *trigger) +{ + (void)user_data; (void)action; (void)arg1; (void)arg2; + (void)database; (void)trigger; + return SQLITE_OK; +} + +static int authorizer_allow2(void *user_data, int action, + const char *arg1, const char *arg2, + const char *database, const char *trigger) +{ + (void)user_data; (void)action; (void)arg1; (void)arg2; + (void)database; (void)trigger; + return SQLITE_OK; +} + +void test_sqlite3_set_authorizer() +{ + sqlite3 *db; + char *err_msg = NULL; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + /* Registration is either accepted (SQLite) or refused (Turso); this is + ** the call PHP makes unconditionally at open, discarding the return. */ + rc = sqlite3_set_authorizer(db, authorizer_allow, NULL); + assert(rc == SQLITE_OK || rc == SQLITE_ERROR); + + /* A permissive (or refused) authorizer must not disturb execution. */ + rc = sqlite3_exec(db, "CREATE TABLE auth_t(a, b)", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + /* Each registration replaces the previous authorizer. */ + rc = sqlite3_set_authorizer(db, authorizer_allow2, (void *)&db); + assert(rc == SQLITE_OK || rc == SQLITE_ERROR); + + rc = sqlite3_exec(db, "INSERT INTO auth_t VALUES (1, 2)", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + /* A NULL callback clears the authorizer. */ + rc = sqlite3_set_authorizer(db, NULL, NULL); + assert(rc == SQLITE_OK || rc == SQLITE_ERROR); + + rc = sqlite3_exec(db, "SELECT a FROM auth_t", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + sqlite3_close(db); + printf("test_sqlite3_set_authorizer test passed\n"); +} + +void test_sqlite3_db_config() +{ + sqlite3 *db; + int value; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + /* SQLite accepts DEFENSIVE and reports state through the out-pointer. + ** Turso refuses it (SQLITE_ERROR, out-pointer untouched): its engine is + ** unconditionally defensive, so there is nothing to toggle — see + ** turso_db_config_int. Either way the out-pointer must never hold + ** garbage. */ + value = -99; + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, &value); + assert(rc == SQLITE_OK || rc == SQLITE_ERROR); + if (rc == SQLITE_OK) { + assert(value == 1); + + /* A negative value queries the current state without changing it. */ + value = -99; + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &value); + assert(rc == SQLITE_OK); + assert(value == 1); + + /* Turning it back off. */ + value = -99; + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, &value); + assert(rc == SQLITE_OK); + assert(value == 0); + } else { + assert(value == -99); + } + + /* A NULL out-pointer must be tolerated whatever the answer; this is the + ** exact call PHP's ext/sqlite3 makes at open when sqlite3.defensive=1, + ** and PHP ignores the return value. */ + rc = sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, (int *)0); + assert(rc == SQLITE_OK || rc == SQLITE_ERROR); + + /* Unknown ops are rejected. */ + rc = sqlite3_db_config(db, 9999, 0, (int *)0); + assert(rc == SQLITE_ERROR); + + sqlite3_close(db); + printf("test_sqlite3_db_config test passed\n"); +} + +void test_sqlite3_extended_result_codes() +{ + sqlite3 *db; + sqlite3_stmt *stmt; + char *err_msg = NULL; + int rc; + + rc = sqlite3_open(":memory:", &db); + assert(rc == SQLITE_OK); + + rc = sqlite3_exec(db, "CREATE TABLE erc_t(a INTEGER PRIMARY KEY, b UNIQUE)", + NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + rc = sqlite3_exec(db, "INSERT INTO erc_t VALUES (1, 1)", NULL, NULL, &err_msg); + assert(rc == SQLITE_OK); + if (err_msg) { sqlite3_free(err_msg); err_msg = NULL; } + + /* Extended result codes are disabled by default: sqlite3_errcode + ** reports only the primary code after a constraint violation. */ + rc = sqlite3_prepare_v2(db, "INSERT INTO erc_t VALUES (2, 1)", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + rc = sqlite3_step(stmt); + assert((rc & 0xff) == SQLITE_CONSTRAINT); + sqlite3_finalize(stmt); + assert(sqlite3_errcode(db) == SQLITE_CONSTRAINT); + /* sqlite3_extended_errcode reports the extended code regardless of the + ** setting; its primary part is the constraint code either way. */ + assert((sqlite3_extended_errcode(db) & 0xff) == SQLITE_CONSTRAINT); + + /* Enabling widens what sqlite3_errcode reports; the primary part of + ** whatever it returns is still the constraint code. */ + rc = sqlite3_extended_result_codes(db, 1); + assert(rc == SQLITE_OK); + rc = sqlite3_prepare_v2(db, "INSERT INTO erc_t VALUES (3, 1)", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + rc = sqlite3_step(stmt); + assert((rc & 0xff) == SQLITE_CONSTRAINT); + sqlite3_finalize(stmt); + assert((sqlite3_errcode(db) & 0xff) == SQLITE_CONSTRAINT); + assert((sqlite3_extended_errcode(db) & 0xff) == SQLITE_CONSTRAINT); + + /* Disabling narrows sqlite3_errcode back to the primary code. */ + rc = sqlite3_extended_result_codes(db, 0); + assert(rc == SQLITE_OK); + rc = sqlite3_prepare_v2(db, "INSERT INTO erc_t VALUES (4, 1)", -1, &stmt, NULL); + assert(rc == SQLITE_OK); + rc = sqlite3_step(stmt); + assert((rc & 0xff) == SQLITE_CONSTRAINT); + sqlite3_finalize(stmt); + assert(sqlite3_errcode(db) == SQLITE_CONSTRAINT); + + sqlite3_close(db); + printf("test_sqlite3_extended_result_codes test passed\n"); +}