mirror of https://github.com/python/cpython
gh-103489: Add get/set config methods to sqlite3.Connection (#103506)
This commit is contained in:
parent
222c63fc6b
commit
bb8aa7a2b4
|
@ -573,6 +573,38 @@ Module constants
|
||||||
package, a third-party library which used to upstream changes to
|
package, a third-party library which used to upstream changes to
|
||||||
:mod:`!sqlite3`. Today, it carries no meaning or practical value.
|
:mod:`!sqlite3`. Today, it carries no meaning or practical value.
|
||||||
|
|
||||||
|
.. _sqlite3-dbconfig-constants:
|
||||||
|
|
||||||
|
.. data:: SQLITE_DBCONFIG_DEFENSIVE
|
||||||
|
SQLITE_DBCONFIG_DQS_DDL
|
||||||
|
SQLITE_DBCONFIG_DQS_DML
|
||||||
|
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_TRIGGER_EQP
|
||||||
|
SQLITE_DBCONFIG_TRUSTED_SCHEMA
|
||||||
|
SQLITE_DBCONFIG_WRITABLE_SCHEMA
|
||||||
|
|
||||||
|
These constants are used for the :meth:`Connection.setconfig`
|
||||||
|
and :meth:`~Connection.getconfig` methods.
|
||||||
|
|
||||||
|
The availability of these constants varies depending on the version of SQLite
|
||||||
|
Python was compiled with.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
|
||||||
|
SQLite docs: Database Connection Configuration Options
|
||||||
|
|
||||||
|
|
||||||
.. _sqlite3-connection-objects:
|
.. _sqlite3-connection-objects:
|
||||||
|
|
||||||
|
@ -1219,6 +1251,30 @@ Connection objects
|
||||||
.. _SQLite limit category: https://www.sqlite.org/c3ref/c_limit_attached.html
|
.. _SQLite limit category: https://www.sqlite.org/c3ref/c_limit_attached.html
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: getconfig(op, /)
|
||||||
|
|
||||||
|
Query a boolean connection configuration option.
|
||||||
|
|
||||||
|
:param int op:
|
||||||
|
A :ref:`SQLITE_DBCONFIG code <sqlite3-dbconfig-constants>`.
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
.. method:: setconfig(op, enable=True, /)
|
||||||
|
|
||||||
|
Set a boolean connection configuration option.
|
||||||
|
|
||||||
|
:param int op:
|
||||||
|
A :ref:`SQLITE_DBCONFIG code <sqlite3-dbconfig-constants>`.
|
||||||
|
|
||||||
|
:param bool enable:
|
||||||
|
``True`` if the configuration option should be enabled (default);
|
||||||
|
``False`` if it should be disabled.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
.. method:: serialize(*, name="main")
|
.. method:: serialize(*, name="main")
|
||||||
|
|
||||||
Serialize a database into a :class:`bytes` object. For an
|
Serialize a database into a :class:`bytes` object. For an
|
||||||
|
|
|
@ -411,6 +411,11 @@ sqlite3
|
||||||
for overriding the SQLite extension entry point.
|
for overriding the SQLite extension entry point.
|
||||||
(Contributed by Erlend E. Aasland in :gh:`103015`.)
|
(Contributed by Erlend E. Aasland in :gh:`103015`.)
|
||||||
|
|
||||||
|
* Add :meth:`~sqlite3.Connection.getconfig` and
|
||||||
|
:meth:`~sqlite3.Connection.setconfig` to :class:`~sqlite3.Connection`
|
||||||
|
to make configuration changes to a database connection.
|
||||||
|
(Contributed by Erlend E. Aasland in :gh:`103489`.)
|
||||||
|
|
||||||
threading
|
threading
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
|
|
@ -577,6 +577,30 @@ class ConnectionTests(unittest.TestCase):
|
||||||
cx.executemany, "insert into t values(?)",
|
cx.executemany, "insert into t values(?)",
|
||||||
((v,) for v in range(3)))
|
((v,) for v in range(3)))
|
||||||
|
|
||||||
|
def test_connection_config(self):
|
||||||
|
op = sqlite.SQLITE_DBCONFIG_ENABLE_FKEY
|
||||||
|
with memory_database() as cx:
|
||||||
|
with self.assertRaisesRegex(ValueError, "unknown"):
|
||||||
|
cx.getconfig(-1)
|
||||||
|
|
||||||
|
# Toggle and verify.
|
||||||
|
old = cx.getconfig(op)
|
||||||
|
new = not old
|
||||||
|
cx.setconfig(op, new)
|
||||||
|
self.assertEqual(cx.getconfig(op), new)
|
||||||
|
|
||||||
|
cx.setconfig(op) # defaults to True
|
||||||
|
self.assertTrue(cx.getconfig(op))
|
||||||
|
|
||||||
|
# Check that foreign key support was actually enabled.
|
||||||
|
with cx:
|
||||||
|
cx.executescript("""
|
||||||
|
create table t(t integer primary key);
|
||||||
|
create table u(u, foreign key(u) references t(t));
|
||||||
|
""")
|
||||||
|
with self.assertRaisesRegex(sqlite.IntegrityError, "constraint"):
|
||||||
|
cx.execute("insert into u values(0)")
|
||||||
|
|
||||||
|
|
||||||
class UninitialisedConnectionTests(unittest.TestCase):
|
class UninitialisedConnectionTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Add :meth:`~sqlite3.Connection.getconfig` and
|
||||||
|
:meth:`~sqlite3.Connection.setconfig` to :class:`~sqlite3.Connection` to
|
||||||
|
make configuration changes to a database connection. Patch by Erlend E.
|
||||||
|
Aasland.
|
|
@ -1568,6 +1568,85 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(setconfig__doc__,
|
||||||
|
"setconfig($self, op, enable=True, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Set a boolean connection configuration option.\n"
|
||||||
|
"\n"
|
||||||
|
" op\n"
|
||||||
|
" The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes.");
|
||||||
|
|
||||||
|
#define SETCONFIG_METHODDEF \
|
||||||
|
{"setconfig", _PyCFunction_CAST(setconfig), METH_FASTCALL, setconfig__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
setconfig_impl(pysqlite_Connection *self, int op, int enable);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
setconfig(pysqlite_Connection *self, PyObject *const *args, Py_ssize_t nargs)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
int op;
|
||||||
|
int enable = 1;
|
||||||
|
|
||||||
|
if (!_PyArg_CheckPositional("setconfig", nargs, 1, 2)) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
op = _PyLong_AsInt(args[0]);
|
||||||
|
if (op == -1 && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
if (nargs < 2) {
|
||||||
|
goto skip_optional;
|
||||||
|
}
|
||||||
|
enable = PyObject_IsTrue(args[1]);
|
||||||
|
if (enable < 0) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
skip_optional:
|
||||||
|
return_value = setconfig_impl(self, op, enable);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(getconfig__doc__,
|
||||||
|
"getconfig($self, op, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Query a boolean connection configuration option.\n"
|
||||||
|
"\n"
|
||||||
|
" op\n"
|
||||||
|
" The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes.");
|
||||||
|
|
||||||
|
#define GETCONFIG_METHODDEF \
|
||||||
|
{"getconfig", (PyCFunction)getconfig, METH_O, getconfig__doc__},
|
||||||
|
|
||||||
|
static int
|
||||||
|
getconfig_impl(pysqlite_Connection *self, int op);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
getconfig(pysqlite_Connection *self, PyObject *arg)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
int op;
|
||||||
|
int _return_value;
|
||||||
|
|
||||||
|
op = _PyLong_AsInt(arg);
|
||||||
|
if (op == -1 && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
_return_value = getconfig_impl(self, op);
|
||||||
|
if ((_return_value == -1) && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = PyBool_FromLong((long)_return_value);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef CREATE_WINDOW_FUNCTION_METHODDEF
|
#ifndef CREATE_WINDOW_FUNCTION_METHODDEF
|
||||||
#define CREATE_WINDOW_FUNCTION_METHODDEF
|
#define CREATE_WINDOW_FUNCTION_METHODDEF
|
||||||
#endif /* !defined(CREATE_WINDOW_FUNCTION_METHODDEF) */
|
#endif /* !defined(CREATE_WINDOW_FUNCTION_METHODDEF) */
|
||||||
|
@ -1587,4 +1666,4 @@ exit:
|
||||||
#ifndef DESERIALIZE_METHODDEF
|
#ifndef DESERIALIZE_METHODDEF
|
||||||
#define DESERIALIZE_METHODDEF
|
#define DESERIALIZE_METHODDEF
|
||||||
#endif /* !defined(DESERIALIZE_METHODDEF) */
|
#endif /* !defined(DESERIALIZE_METHODDEF) */
|
||||||
/*[clinic end generated code: output=833f0e27cd3a4560 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=8b03149c115ee6da input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
#include "prepare_protocol.h"
|
#include "prepare_protocol.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
#if SQLITE_VERSION_NUMBER >= 3014000
|
#if SQLITE_VERSION_NUMBER >= 3014000
|
||||||
#define HAVE_TRACE_V2
|
#define HAVE_TRACE_V2
|
||||||
#endif
|
#endif
|
||||||
|
@ -2343,6 +2345,119 @@ getlimit_impl(pysqlite_Connection *self, int category)
|
||||||
return setlimit_impl(self, category, -1);
|
return setlimit_impl(self, category, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
is_int_config(const int op)
|
||||||
|
{
|
||||||
|
switch (op) {
|
||||||
|
case SQLITE_DBCONFIG_ENABLE_FKEY:
|
||||||
|
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3012002
|
||||||
|
case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3013000
|
||||||
|
case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3016000
|
||||||
|
case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3020000
|
||||||
|
case SQLITE_DBCONFIG_ENABLE_QPSG:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3022000
|
||||||
|
case SQLITE_DBCONFIG_TRIGGER_EQP:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3024000
|
||||||
|
case SQLITE_DBCONFIG_RESET_DATABASE:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3026000
|
||||||
|
case SQLITE_DBCONFIG_DEFENSIVE:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3028000
|
||||||
|
case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3029000
|
||||||
|
case SQLITE_DBCONFIG_DQS_DDL:
|
||||||
|
case SQLITE_DBCONFIG_DQS_DML:
|
||||||
|
case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3030000
|
||||||
|
case SQLITE_DBCONFIG_ENABLE_VIEW:
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3031000
|
||||||
|
case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
|
||||||
|
case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_sqlite3.Connection.setconfig as setconfig
|
||||||
|
|
||||||
|
op: int
|
||||||
|
The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes.
|
||||||
|
enable: bool = True
|
||||||
|
/
|
||||||
|
|
||||||
|
Set a boolean connection configuration option.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
setconfig_impl(pysqlite_Connection *self, int op, int enable)
|
||||||
|
/*[clinic end generated code: output=c60b13e618aff873 input=a10f1539c2d7da6b]*/
|
||||||
|
{
|
||||||
|
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!is_int_config(op)) {
|
||||||
|
return PyErr_Format(PyExc_ValueError, "unknown config 'op': %d", op);
|
||||||
|
}
|
||||||
|
|
||||||
|
int actual;
|
||||||
|
int rc = sqlite3_db_config(self->db, op, enable, &actual);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
(void)_pysqlite_seterror(self->state, self->db);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (enable != actual) {
|
||||||
|
PyErr_SetString(self->state->OperationalError, "Unable to set config");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_sqlite3.Connection.getconfig as getconfig -> bool
|
||||||
|
|
||||||
|
op: int
|
||||||
|
The configuration verb; one of the sqlite3.SQLITE_DBCONFIG codes.
|
||||||
|
/
|
||||||
|
|
||||||
|
Query a boolean connection configuration option.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static int
|
||||||
|
getconfig_impl(pysqlite_Connection *self, int op)
|
||||||
|
/*[clinic end generated code: output=25ac05044c7b78a3 input=b0526d7e432e3f2f]*/
|
||||||
|
{
|
||||||
|
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!is_int_config(op)) {
|
||||||
|
PyErr_Format(PyExc_ValueError, "unknown config 'op': %d", op);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int current;
|
||||||
|
int rc = sqlite3_db_config(self->db, op, -1, ¤t);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
(void)_pysqlite_seterror(self->state, self->db);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx))
|
get_autocommit(pysqlite_Connection *self, void *Py_UNUSED(ctx))
|
||||||
|
@ -2424,6 +2539,8 @@ static PyMethodDef connection_methods[] = {
|
||||||
DESERIALIZE_METHODDEF
|
DESERIALIZE_METHODDEF
|
||||||
CREATE_WINDOW_FUNCTION_METHODDEF
|
CREATE_WINDOW_FUNCTION_METHODDEF
|
||||||
BLOBOPEN_METHODDEF
|
BLOBOPEN_METHODDEF
|
||||||
|
SETCONFIG_METHODDEF
|
||||||
|
GETCONFIG_METHODDEF
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -499,6 +499,49 @@ add_integer_constants(PyObject *module) {
|
||||||
#if SQLITE_VERSION_NUMBER >= 3008007
|
#if SQLITE_VERSION_NUMBER >= 3008007
|
||||||
ADD_INT(SQLITE_LIMIT_WORKER_THREADS);
|
ADD_INT(SQLITE_LIMIT_WORKER_THREADS);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Database connection configuration options.
|
||||||
|
* See https://www.sqlite.org/c3ref/c_dbconfig_defensive.html
|
||||||
|
*/
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_ENABLE_FKEY);
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_ENABLE_TRIGGER);
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3012002
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3013000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3016000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3020000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_ENABLE_QPSG);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3022000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_TRIGGER_EQP);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3024000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_RESET_DATABASE);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3026000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_DEFENSIVE);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3028000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_WRITABLE_SCHEMA);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3029000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_DQS_DDL);
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_DQS_DML);
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_LEGACY_ALTER_TABLE);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3030000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_ENABLE_VIEW);
|
||||||
|
#endif
|
||||||
|
#if SQLITE_VERSION_NUMBER >= 3031000
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT);
|
||||||
|
ADD_INT(SQLITE_DBCONFIG_TRUSTED_SCHEMA);
|
||||||
|
#endif
|
||||||
#undef ADD_INT
|
#undef ADD_INT
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue