Skip to content

Commit bb8aa7a

Browse files
gh-103489: Add get/set config methods to sqlite3.Connection (#103506)
1 parent 222c63f commit bb8aa7a

File tree

7 files changed

+329
-1
lines changed

7 files changed

+329
-1
lines changed

Doc/library/sqlite3.rst

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,38 @@ Module constants
573573
package, a third-party library which used to upstream changes to
574574
:mod:`!sqlite3`. Today, it carries no meaning or practical value.
575575

576+
.. _sqlite3-dbconfig-constants:
577+
578+
.. data:: SQLITE_DBCONFIG_DEFENSIVE
579+
SQLITE_DBCONFIG_DQS_DDL
580+
SQLITE_DBCONFIG_DQS_DML
581+
SQLITE_DBCONFIG_ENABLE_FKEY
582+
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
583+
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
584+
SQLITE_DBCONFIG_ENABLE_QPSG
585+
SQLITE_DBCONFIG_ENABLE_TRIGGER
586+
SQLITE_DBCONFIG_ENABLE_VIEW
587+
SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
588+
SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
589+
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
590+
SQLITE_DBCONFIG_RESET_DATABASE
591+
SQLITE_DBCONFIG_TRIGGER_EQP
592+
SQLITE_DBCONFIG_TRUSTED_SCHEMA
593+
SQLITE_DBCONFIG_WRITABLE_SCHEMA
594+
595+
These constants are used for the :meth:`Connection.setconfig`
596+
and :meth:`~Connection.getconfig` methods.
597+
598+
The availability of these constants varies depending on the version of SQLite
599+
Python was compiled with.
600+
601+
.. versionadded:: 3.12
602+
603+
.. seealso::
604+
605+
https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
606+
SQLite docs: Database Connection Configuration Options
607+
576608

577609
.. _sqlite3-connection-objects:
578610

@@ -1219,6 +1251,30 @@ Connection objects
12191251
.. _SQLite limit category: https://www.sqlite.org/c3ref/c_limit_attached.html
12201252

12211253

1254+
.. method:: getconfig(op, /)
1255+
1256+
Query a boolean connection configuration option.
1257+
1258+
:param int op:
1259+
A :ref:`SQLITE_DBCONFIG code <sqlite3-dbconfig-constants>`.
1260+
1261+
:rtype: bool
1262+
1263+
.. versionadded:: 3.12
1264+
1265+
.. method:: setconfig(op, enable=True, /)
1266+
1267+
Set a boolean connection configuration option.
1268+
1269+
:param int op:
1270+
A :ref:`SQLITE_DBCONFIG code <sqlite3-dbconfig-constants>`.
1271+
1272+
:param bool enable:
1273+
``True`` if the configuration option should be enabled (default);
1274+
``False`` if it should be disabled.
1275+
1276+
.. versionadded:: 3.12
1277+
12221278
.. method:: serialize(*, name="main")
12231279

12241280
Serialize a database into a :class:`bytes` object. For an

Doc/whatsnew/3.12.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ sqlite3
411411
for overriding the SQLite extension entry point.
412412
(Contributed by Erlend E. Aasland in :gh:`103015`.)
413413

414+
* Add :meth:`~sqlite3.Connection.getconfig` and
415+
:meth:`~sqlite3.Connection.setconfig` to :class:`~sqlite3.Connection`
416+
to make configuration changes to a database connection.
417+
(Contributed by Erlend E. Aasland in :gh:`103489`.)
418+
414419
threading
415420
---------
416421

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,30 @@ def test_connection_bad_reinit(self):
577577
cx.executemany, "insert into t values(?)",
578578
((v,) for v in range(3)))
579579

580+
def test_connection_config(self):
581+
op = sqlite.SQLITE_DBCONFIG_ENABLE_FKEY
582+
with memory_database() as cx:
583+
with self.assertRaisesRegex(ValueError, "unknown"):
584+
cx.getconfig(-1)
585+
586+
# Toggle and verify.
587+
old = cx.getconfig(op)
588+
new = not old
589+
cx.setconfig(op, new)
590+
self.assertEqual(cx.getconfig(op), new)
591+
592+
cx.setconfig(op) # defaults to True
593+
self.assertTrue(cx.getconfig(op))
594+
595+
# Check that foreign key support was actually enabled.
596+
with cx:
597+
cx.executescript("""
598+
create table t(t integer primary key);
599+
create table u(u, foreign key(u) references t(t));
600+
""")
601+
with self.assertRaisesRegex(sqlite.IntegrityError, "constraint"):
602+
cx.execute("insert into u values(0)")
603+
580604

581605
class UninitialisedConnectionTests(unittest.TestCase):
582606
def setUp(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add :meth:`~sqlite3.Connection.getconfig` and
2+
:meth:`~sqlite3.Connection.setconfig` to :class:`~sqlite3.Connection` to
3+
make configuration changes to a database connection. Patch by Erlend E.
4+
Aasland.

Modules/_sqlite/clinic/connection.c.h

Lines changed: 80 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/_sqlite/connection.c

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#include "prepare_protocol.h"
3131
#include "util.h"
3232

33+
#include <stdbool.h>
34+
3335
#if SQLITE_VERSION_NUMBER >= 3014000
3436
#define HAVE_TRACE_V2
3537
#endif
@@ -2343,6 +2345,119 @@ getlimit_impl(pysqlite_Connection *self, int category)
23432345
return setlimit_impl(self, category, -1);
23442346
}
23452347

2348+
static inline bool
2349+
is_int_config(const int op)
2350+
{
2351+
switch (op) {
2352+
case SQLITE_DBCONFIG_ENABLE_FKEY:
2353+
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
2354+
#if SQLITE_VERSION_NUMBER >= 3012002
2355+
case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
2356+
#endif
2357+
#if SQLITE_VERSION_NUMBER >= 3013000
2358+
case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
2359+
#endif
2360+
#if SQLITE_VERSION_NUMBER >= 3016000
2361+
case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
2362+
#endif
2363+
#if SQLITE_VERSION_NUMBER >= 3020000
2364+
case SQLITE_DBCONFIG_ENABLE_QPSG:
2365+
#endif
2366+
#if SQLITE_VERSION_NUMBER >= 3022000
2367+
case SQLITE_DBCONFIG_TRIGGER_EQP:
2368+
#endif
2369+
#if SQLITE_VERSION_NUMBER >= 3024000
2370+
case SQLITE_DBCONFIG_RESET_DATABASE:
2371+
#endif
2372+
#if SQLITE_VERSION_NUMBER >= 3026000
2373+
case SQLITE_DBCONFIG_DEFENSIVE:
2374+
#endif
2375+
#if SQLITE_VERSION_NUMBER >= 3028000
2376+
case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
2377+
#endif
2378+
#if SQLITE_VERSION_NUMBER >= 3029000
2379+
case SQLITE_DBCONFIG_DQS_DDL:
2380+
case SQLITE_DBCONFIG_DQS_DML:
2381+
case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
2382+
#endif
2383+
#if SQLITE_VERSION_NUMBER >= 3030000
2384+
case SQLITE_DBCONFIG_ENABLE_VIEW:
2385+
#endif
2386+
#if SQLITE_VERSION_NUMBER >= 3031000
2387+
case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
2388+
case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
2389+
#endif
2390+
return true;
2391+
default:
2392+
return false;
2393+
}
2394+
}
2395+
2396+
/*[clinic input]
2397+
_sqlite3.Connection.setconfig as setconfig
2398+
2399+
op: int
2400+
The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes.
2401+
enable: bool = True
2402+
/
2403+
2404+
Set a boolean connection configuration option.
2405+
[clinic start generated code]*/
2406+
2407+
static PyObject *
2408+
setconfig_impl(pysqlite_Connection *self, int op, int enable)
2409+
/*[clinic end generated code: output=c60b13e618aff873 input=a10f1539c2d7da6b]*/
2410+
{
2411+
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
2412+
return NULL;
2413+
}
2414+
if (!is_int_config(op)) {
2415+
return PyErr_Format(PyExc_ValueError, "unknown config 'op': %d", op);
2416+
}
2417+
2418+
int actual;
2419+
int rc = sqlite3_db_config(self->db, op, enable, &actual);
2420+
if (rc != SQLITE_OK) {
2421+
(void)_pysqlite_seterror(self->state, self->db);
2422+
return NULL;
2423+
}
2424+
if (enable != actual) {
2425+
PyErr_SetString(self->state->OperationalError, "Unable to set config");
2426+
return NULL;
2427+
}
2428+
Py_RETURN_NONE;
2429+
}
2430+
2431+
/*[clinic input]
2432+
_sqlite3.Connection.getconfig as getconfig -> bool
2433+
2434+
op: int
2435+
The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes.
2436+
/
2437+
2438+
Query a boolean connection configuration option.
2439+
[clinic start generated code]*/
2440+
2441+
static int
2442+
getconfig_impl(pysqlite_Connection *self, int op)
2443+
/*[clinic end generated code: output=25ac05044c7b78a3 input=b0526d7e432e3f2f]*/
2444+
{
2445+
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
2446+
return -1;
2447+
}
2448+
if (!is_int_config(op)) {
2449+
PyErr_Format(PyExc_ValueError, "unknown config 'op': %d", op);
2450+
return -1;
2451+
}
2452+
2453+
int current;
2454+
int rc = sqlite3_db_config(self->db, op, -1, &current);
2455+
if (rc != SQLITE_OK) {
2456+
(void)_pysqlite_seterror(self->state, self->db);
2457+
return -1;
2458+
}
2459+
return current;
2460+
}
23462461

23472462
static PyObject *
23482463
get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx))
@@ -2424,6 +2539,8 @@ static PyMethodDef connection_methods[] = {
24242539
DESERIALIZE_METHODDEF
24252540
CREATE_WINDOW_FUNCTION_METHODDEF
24262541
BLOBOPEN_METHODDEF
2542+
SETCONFIG_METHODDEF
2543+
GETCONFIG_METHODDEF
24272544
{NULL, NULL}
24282545
};
24292546

Modules/_sqlite/module.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,49 @@ add_integer_constants(PyObject *module) {
499499
#if SQLITE_VERSION_NUMBER >= 3008007
500500
ADD_INT(SQLITE_LIMIT_WORKER_THREADS);
501501
#endif
502+
503+
/*
504+
* Database connection configuration options.
505+
* See https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
506+
*/
507+
ADD_INT(SQLITE_DBCONFIG_ENABLE_FKEY);
508+
ADD_INT(SQLITE_DBCONFIG_ENABLE_TRIGGER);
509+
#if SQLITE_VERSION_NUMBER >= 3012002
510+
ADD_INT(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER);
511+
#endif
512+
#if SQLITE_VERSION_NUMBER >= 3013000
513+
ADD_INT(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION);
514+
#endif
515+
#if SQLITE_VERSION_NUMBER >= 3016000
516+
ADD_INT(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE);
517+
#endif
518+
#if SQLITE_VERSION_NUMBER >= 3020000
519+
ADD_INT(SQLITE_DBCONFIG_ENABLE_QPSG);
520+
#endif
521+
#if SQLITE_VERSION_NUMBER >= 3022000
522+
ADD_INT(SQLITE_DBCONFIG_TRIGGER_EQP);
523+
#endif
524+
#if SQLITE_VERSION_NUMBER >= 3024000
525+
ADD_INT(SQLITE_DBCONFIG_RESET_DATABASE);
526+
#endif
527+
#if SQLITE_VERSION_NUMBER >= 3026000
528+
ADD_INT(SQLITE_DBCONFIG_DEFENSIVE);
529+
#endif
530+
#if SQLITE_VERSION_NUMBER >= 3028000
531+
ADD_INT(SQLITE_DBCONFIG_WRITABLE_SCHEMA);
532+
#endif
533+
#if SQLITE_VERSION_NUMBER >= 3029000
534+
ADD_INT(SQLITE_DBCONFIG_DQS_DDL);
535+
ADD_INT(SQLITE_DBCONFIG_DQS_DML);
536+
ADD_INT(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE);
537+
#endif
538+
#if SQLITE_VERSION_NUMBER >= 3030000
539+
ADD_INT(SQLITE_DBCONFIG_ENABLE_VIEW);
540+
#endif
541+
#if SQLITE_VERSION_NUMBER >= 3031000
542+
ADD_INT(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT);
543+
ADD_INT(SQLITE_DBCONFIG_TRUSTED_SCHEMA);
544+
#endif
502545
#undef ADD_INT
503546
return 0;
504547
}

0 commit comments

Comments
 (0)