sqlite3: add SQLITE_DBCONFIG constants, setconfig() and getconfig() (#8535)

* sqlite3: add SQLITE_DBCONFIG constants, setconfig() and getconfig()

Implement Connection.setconfig() and Connection.getconfig() using
sqlite3_db_config(), and export all SQLITE_DBCONFIG_* integer
constants to the module.

Fixes test_connection_config in test_dbapi.py.

Assisted-by: GitHub Copilot:claude-sonnet-4-6

* sqlite3: fix setconfig to propagate actual SQLite error

When sqlite3_db_config() returns a non-OK result, propagate the actual
SQLite error instead of replacing it with a generic 'Unable to set
config' message, to match CPython's set_error_from_db() behavior.

Assisted-by: GitHub Copilot:claude-sonnet-4-6
This commit is contained in:
Jiseok CHOI authored and GitHub committed 2026-08-16 20:13:10 +09:00
1 parent 70b47dd7c7
commit 7eb6d38c88
2 files changed
+77 -7

No files matched your search

-1
View File
@@ -525,7 +525,6 @@ class ConnectionTests(unittest.TestCase):
cx.executemany, "insert into t values(?)",
((v,) for v in range(3)))
@unittest.expectedFailure # TODO: RUSTPYTHON; SQLITE_DBCONFIG constants not implemented
def test_connection_config(self):
op = sqlite.SQLITE_DBCONFIG_ENABLE_FKEY
with memory_database() as cx:
+77 -6
View File
@@ -31,11 +31,11 @@ mod _sqlite3 {
sqlite3_column_double, sqlite3_column_int64, sqlite3_column_name, sqlite3_column_text,
sqlite3_column_type, sqlite3_complete, sqlite3_context, sqlite3_context_db_handle,
sqlite3_create_collation_v2, sqlite3_create_function_v2, sqlite3_create_window_function,
sqlite3_data_count, sqlite3_db_handle, sqlite3_errcode, sqlite3_errmsg, sqlite3_exec,
sqlite3_expanded_sql, sqlite3_extended_errcode, sqlite3_finalize, sqlite3_get_autocommit,
sqlite3_interrupt, sqlite3_last_insert_rowid, sqlite3_libversion, sqlite3_limit,
sqlite3_open_v2, sqlite3_prepare_v2, sqlite3_progress_handler, sqlite3_reset,
sqlite3_result_blob, sqlite3_result_double, sqlite3_result_error,
sqlite3_data_count, sqlite3_db_config, sqlite3_db_handle, sqlite3_errcode, sqlite3_errmsg,
sqlite3_exec, sqlite3_expanded_sql, sqlite3_extended_errcode, sqlite3_finalize,
sqlite3_get_autocommit, sqlite3_interrupt, sqlite3_last_insert_rowid, sqlite3_libversion,
sqlite3_limit, sqlite3_open_v2, sqlite3_prepare_v2, sqlite3_progress_handler,
sqlite3_reset, sqlite3_result_blob, sqlite3_result_double, sqlite3_result_error,
sqlite3_result_error_nomem, sqlite3_result_error_toobig, sqlite3_result_int64,
sqlite3_result_null, sqlite3_result_text, sqlite3_set_authorizer, sqlite3_sleep,
sqlite3_step, sqlite3_stmt, sqlite3_stmt_busy, sqlite3_stmt_readonly, sqlite3_threadsafe,
@@ -161,7 +161,17 @@ mod _sqlite3 {
SQLITE_ALTER_TABLE, SQLITE_ANALYZE, SQLITE_ATTACH, SQLITE_CREATE_INDEX,
SQLITE_CREATE_TABLE, SQLITE_CREATE_TEMP_INDEX, SQLITE_CREATE_TEMP_TABLE,
SQLITE_CREATE_TEMP_TRIGGER, SQLITE_CREATE_TEMP_VIEW, SQLITE_CREATE_TRIGGER,
SQLITE_CREATE_VIEW, SQLITE_CREATE_VTABLE, SQLITE_DELETE, SQLITE_DENY, SQLITE_DETACH,
SQLITE_CREATE_VIEW, SQLITE_CREATE_VTABLE, SQLITE_DBCONFIG_DEFENSIVE,
SQLITE_DBCONFIG_DQS_DDL, SQLITE_DBCONFIG_DQS_DML, SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE,
SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE, SQLITE_DBCONFIG_ENABLE_COMMENTS,
SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_DBCONFIG_ENABLE_QPSG,
SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_DBCONFIG_ENABLE_VIEW,
SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, SQLITE_DBCONFIG_LEGACY_FILE_FORMAT,
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_DBCONFIG_RESET_DATABASE,
SQLITE_DBCONFIG_REVERSE_SCANORDER, SQLITE_DBCONFIG_STMT_SCANSTATUS,
SQLITE_DBCONFIG_TRIGGER_EQP, SQLITE_DBCONFIG_TRUSTED_SCHEMA,
SQLITE_DBCONFIG_WRITABLE_SCHEMA, SQLITE_DELETE, SQLITE_DENY, SQLITE_DETACH,
SQLITE_DROP_INDEX, SQLITE_DROP_TABLE, SQLITE_DROP_TEMP_INDEX, SQLITE_DROP_TEMP_TABLE,
SQLITE_DROP_TEMP_TRIGGER, SQLITE_DROP_TEMP_VIEW, SQLITE_DROP_TRIGGER, SQLITE_DROP_VIEW,
SQLITE_DROP_VTABLE, SQLITE_FUNCTION, SQLITE_IGNORE, SQLITE_INSERT, SQLITE_LIMIT_ATTACHED,
@@ -1519,6 +1529,39 @@ mod _sqlite3 {
self.db_lock(vm)?.limit(category, limit, vm)
}
#[pymethod]
fn setconfig(
&self,
op: c_int,
enable: OptionalArg<bool>,
vm: &VirtualMachine,
) -> PyResult<()> {
let db = self.db_lock(vm)?;
if !is_int_dbconfig(op) {
return Err(vm.new_value_error(format!("unknown config 'op': {op}")));
}
let enable = enable.unwrap_or(true) as c_int;
let mut actual: c_int = 0;
let rc = unsafe { sqlite3_db_config(db.db, op, enable, &mut actual) };
db.check(rc, vm)?;
if enable != actual {
return Err(new_operational_error(vm, "Unable to set config".to_owned()));
}
Ok(())
}
#[pymethod]
fn getconfig(&self, op: c_int, vm: &VirtualMachine) -> PyResult<bool> {
let db = self.db_lock(vm)?;
if !is_int_dbconfig(op) {
return Err(vm.new_value_error(format!("unknown config 'op': {op}")));
}
let mut current: c_int = 0;
let rc = unsafe { sqlite3_db_config(db.db, op, -1, &mut current) };
db.check(rc, vm)?;
Ok(current != 0)
}
#[pymethod]
fn __enter__(zelf: PyRef<Self>) -> PyRef<Self> {
zelf
@@ -3537,6 +3580,34 @@ mod _sqlite3 {
Ok(())
}
fn is_int_dbconfig(op: c_int) -> bool {
use libsqlite3_sys::*;
matches!(
op,
SQLITE_DBCONFIG_ENABLE_FKEY
| SQLITE_DBCONFIG_ENABLE_TRIGGER
| SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
| SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
| SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
| SQLITE_DBCONFIG_ENABLE_QPSG
| SQLITE_DBCONFIG_TRIGGER_EQP
| SQLITE_DBCONFIG_RESET_DATABASE
| SQLITE_DBCONFIG_DEFENSIVE
| SQLITE_DBCONFIG_WRITABLE_SCHEMA
| SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
| SQLITE_DBCONFIG_DQS_DDL
| SQLITE_DBCONFIG_DQS_DML
| SQLITE_DBCONFIG_ENABLE_VIEW
| SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
| SQLITE_DBCONFIG_TRUSTED_SCHEMA
| SQLITE_DBCONFIG_STMT_SCANSTATUS
| SQLITE_DBCONFIG_REVERSE_SCANORDER
| SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE
| SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE
| SQLITE_DBCONFIG_ENABLE_COMMENTS
)
}
fn ptr_to_str<'a>(p: *const libc::c_char, vm: &VirtualMachine) -> PyResult<&'a str> {
if p.is_null() {
return Err(vm.new_memory_error("string pointer is null"));