diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 096892b605b99c..55da9f58c79248 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -579,6 +579,61 @@ Module constants .. deprecated-removed:: 3.12 3.14 The :data:`!version` and :data:`!version_info` constants. +.. _sqlite3-fcntl-constants: + +.. data:: SQLITE_FCNTL_LOCKSTATE + SQLITE_FCNTL_GET_LOCKPROXYFILE + SQLITE_FCNTL_SET_LOCKPROXYFILE + SQLITE_FCNTL_LAST_ERRNO + SQLITE_FCNTL_SIZE_HINT + SQLITE_FCNTL_CHUNK_SIZE + SQLITE_FCNTL_FILE_POINTER + SQLITE_FCNTL_SYNC_OMITTED + SQLITE_FCNTL_WIN32_AV_RETRY + SQLITE_FCNTL_PERSIST_WAL + SQLITE_FCNTL_OVERWRITE + SQLITE_FCNTL_POWERSAFE_OVERWRITE + SQLITE_FCNTL_PRAGMA + SQLITE_FCNTL_BUSYHANDLER + SQLITE_FCNTL_MMAP_SIZE + SQLITE_FCNTL_TRACE + SQLITE_FCNTL_HAS_MOVED + SQLITE_FCNTL_SYNC + SQLITE_FCNTL_COMMIT_PHASETWO + SQLITE_FCNTL_WIN32_SET_HANDLE + SQLITE_FCNTL_WAL_BLOCK + SQLITE_FCNTL_ZIPVFS + SQLITE_FCNTL_RBU + SQLITE_FCNTL_VFS_POINTER + SQLITE_FCNTL_JOURNAL_POINTER + SQLITE_FCNTL_WIN32_GET_HANDLE + SQLITE_FCNTL_PDB + SQLITE_FCNTL_BEGIN_ATOMIC_WRITE + SQLITE_FCNTL_COMMIT_ATOMIC_WRITE + SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE + SQLITE_FCNTL_LOCK_TIMEOUT + SQLITE_FCNTL_DATA_VERSION + SQLITE_FCNTL_SIZE_LIMIT + SQLITE_FCNTL_CKPT_DONE + SQLITE_FCNTL_RESERVE_BYTES + SQLITE_FCNTL_CKPT_START + SQLITE_FCNTL_EXTERNAL_READER + SQLITE_FCNTL_CKSM_FILE + SQLITE_FCNTL_RESET_CACHE + SQLITE_FCNTL_NULL_IO + + These constants are used for the :meth:`Connection.file_control` method. + + The availability of these constants varies depending on the version of SQLite + Python was compiled with. + + .. versionadded:: 3.14 + + .. seealso:: + + https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html + SQLite docs: Standard File Control Opcodes + .. _sqlite3-connection-objects: Connection objects @@ -1288,6 +1343,24 @@ Connection objects .. versionadded:: 3.12 + .. method:: file_control(op, val, /, name="main") + + Invoke a file control method on the database. + Opcodes which take non-integer arguments are not supported. + + :param int op: + The :ref:`SQLITE_FCNTL_* constant ` to invoke. + + :param int arg: + The argument to pass to the operation. + + :param str name: + the database name to operate against. + + :rtype: int + + .. versionadded:: 3.14 + .. method:: serialize(*, name="main") Serialize a database into a :class:`bytes` object. For an diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py index c3aa3bf2d7bd9f..0097d0b50c2119 100644 --- a/Lib/test/test_sqlite3/test_dbapi.py +++ b/Lib/test/test_sqlite3/test_dbapi.py @@ -25,6 +25,7 @@ import sqlite3 as sqlite import subprocess import sys +import tempfile import threading import unittest import urllib.parse @@ -724,6 +725,31 @@ def test_database_keyword(self): with contextlib.closing(sqlite.connect(database=":memory:")) as cx: self.assertEqual(type(cx), sqlite.Connection) + @unittest.skipIf(sys.platform == "darwin", "skipped on macOS") + def test_wal_preservation(self): + with tempfile.TemporaryDirectory() as dirname: + path = os.path.join(dirname, "db.sqlite") + with contextlib.closing(sqlite.connect(path)) as cx: + cx.file_control(sqlite.SQLITE_FCNTL_PERSIST_WAL, 1) + cu = cx.cursor() + cu.execute("PRAGMA journal_mode = WAL") + cu.execute("CREATE TABLE foo (id int)") + cu.execute("INSERT INTO foo (id) VALUES (1)") + self.assertTrue(os.path.exists(path + "-wal")) + self.assertTrue(os.path.exists(path + "-wal")) + + with contextlib.closing(sqlite.connect(path)) as cx: + cu = cx.cursor() + self.assertTrue(os.path.exists(path + "-wal")) + cu.execute("INSERT INTO foo (id) VALUES (2)") + self.assertFalse(os.path.exists(path + "-wal")) + + + def test_file_control_raises(self): + with memory_database() as cx: + with self.assertRaises(sqlite.InternalError): + cx.file_control(sqlite.SQLITE_FCNTL_PERSIST_WAL, 1) + class CursorTests(unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2025-01-05-00-55-41.gh-issue-128505.Nf5FY2.rst b/Misc/NEWS.d/next/Library/2025-01-05-00-55-41.gh-issue-128505.Nf5FY2.rst new file mode 100644 index 00000000000000..42dc829a444618 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-05-00-55-41.gh-issue-128505.Nf5FY2.rst @@ -0,0 +1,3 @@ +sqlite Connection objects now expose a method +:meth:`sqlite3.Connection.file_control`, which is a thin wrapper for +`sqlite3_file_control `_. diff --git a/Modules/_sqlite/blob.c b/Modules/_sqlite/blob.c index 390375628bfb4f..35d090e3ca2dce 100644 --- a/Modules/_sqlite/blob.c +++ b/Modules/_sqlite/blob.c @@ -119,7 +119,7 @@ static void blob_seterror(pysqlite_Blob *self, int rc) { assert(self->connection != NULL); - _pysqlite_seterror(self->connection->state, self->connection->db); + set_error_from_db(self->connection->state, self->connection->db); } static PyObject * diff --git a/Modules/_sqlite/clinic/connection.c.h b/Modules/_sqlite/clinic/connection.c.h index 82fba44eb1b074..27b0714c9b7eaa 100644 --- a/Modules/_sqlite/clinic/connection.c.h +++ b/Modules/_sqlite/clinic/connection.c.h @@ -1455,6 +1455,99 @@ pysqlite_connection_create_collation(PyObject *self, PyTypeObject *cls, PyObject return return_value; } +PyDoc_STRVAR(pysqlite_connection_file_control__doc__, +"file_control($self, op, arg, /, name=\'main\')\n" +"--\n" +"\n" +"Invoke a file control method on the database.\n" +"\n" +" op\n" +" The SQLITE_FCNTL_* constant to invoke.\n" +" arg\n" +" The argument to pass to the operation.\n" +" name\n" +" The database name to operate against.\n" +"\n" +"Opcodes which take non-integer arguments are not supported."); + +#define PYSQLITE_CONNECTION_FILE_CONTROL_METHODDEF \ + {"file_control", _PyCFunction_CAST(pysqlite_connection_file_control), METH_FASTCALL|METH_KEYWORDS, pysqlite_connection_file_control__doc__}, + +static PyObject * +pysqlite_connection_file_control_impl(pysqlite_Connection *self, int op, + int arg, const char *name); + +static PyObject * +pysqlite_connection_file_control(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(name), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"", "", "name", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "file_control", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + int op; + int arg; + const char *name = "main"; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + op = PyLong_AsInt(args[0]); + if (op == -1 && PyErr_Occurred()) { + goto exit; + } + arg = PyLong_AsInt(args[1]); + if (arg == -1 && PyErr_Occurred()) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (!PyUnicode_Check(args[2])) { + _PyArg_BadArgument("file_control", "argument 'name'", "str", args[2]); + goto exit; + } + Py_ssize_t name_length; + name = PyUnicode_AsUTF8AndSize(args[2], &name_length); + if (name == NULL) { + goto exit; + } + if (strlen(name) != (size_t)name_length) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + goto exit; + } +skip_optional_pos: + return_value = pysqlite_connection_file_control_impl((pysqlite_Connection *)self, op, arg, name); + +exit: + return return_value; +} + #if defined(PY_SQLITE_HAVE_SERIALIZE) PyDoc_STRVAR(serialize__doc__, @@ -1881,4 +1974,4 @@ getconfig(PyObject *self, PyObject *arg) #ifndef DESERIALIZE_METHODDEF #define DESERIALIZE_METHODDEF #endif /* !defined(DESERIALIZE_METHODDEF) */ -/*[clinic end generated code: output=c59effb407b8ea4d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6a1cff4b2d430037 input=a9049054013a1b77]*/ diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 16afd7eada113f..16d61b755bfc29 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -188,7 +188,7 @@ connection_exec_stmt(pysqlite_Connection *self, const char *sql) Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return -1; } return 0; @@ -274,7 +274,7 @@ pysqlite_connection_init_impl(pysqlite_Connection *self, PyObject *database, pysqlite_state *state = pysqlite_get_state_by_type(Py_TYPE(self)); if (rc != SQLITE_OK) { - _pysqlite_seterror(state, db); + set_error_from_db(state, db); goto error; } @@ -607,11 +607,11 @@ blobopen_impl(pysqlite_Connection *self, const char *table, const char *col, Py_END_ALLOW_THREADS if (rc == SQLITE_MISUSE) { - PyErr_Format(self->state->InterfaceError, sqlite3_errstr(rc)); + set_error_from_code(self->state, rc); return NULL; } else if (rc != SQLITE_OK) { - _pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } @@ -1307,6 +1307,12 @@ create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls, "SQLite 3.25.0 or higher"); return NULL; } + int limit = sqlite3_limit(self->db, SQLITE_LIMIT_FUNCTION_ARG, -1); + if (num_params < -1 || num_params > limit) { + return PyErr_Format(self->ProgrammingError, + "'num_params' must be between -1 and %d, not %d", + limit, num_params); + } if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { return NULL; @@ -1333,9 +1339,9 @@ create_window_function_impl(pysqlite_Connection *self, PyTypeObject *cls, } if (rc != SQLITE_OK) { - // Errors are not set on the database connection, so we cannot - // use _pysqlite_seterror(). - PyErr_SetString(self->ProgrammingError, sqlite3_errstr(rc)); + /* Errors are not set on the database connection; use result code + * instead. */ + set_error_from_code(self->state, rc); return NULL; } Py_RETURN_NONE; @@ -2090,7 +2096,7 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self, Py_END_ALLOW_THREADS if (bck_handle == NULL) { - _pysqlite_seterror(self->state, bck_conn); + set_error_from_db(self->state, bck_conn); return NULL; } @@ -2128,7 +2134,7 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self, Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - _pysqlite_seterror(self->state, bck_conn); + set_error_from_db(self->state, bck_conn); return NULL; } @@ -2186,13 +2192,129 @@ pysqlite_connection_create_collation_impl(pysqlite_Connection *self, if (callable != Py_None) { free_callback_context(ctx); } - _pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } Py_RETURN_NONE; } +static inline bool +is_int_fcntl(const int op) +{ + switch (op) { + case SQLITE_FCNTL_LOCKSTATE: + case SQLITE_FCNTL_GET_LOCKPROXYFILE: + case SQLITE_FCNTL_SET_LOCKPROXYFILE: + case SQLITE_FCNTL_LAST_ERRNO: + case SQLITE_FCNTL_SIZE_HINT: + case SQLITE_FCNTL_CHUNK_SIZE: + case SQLITE_FCNTL_FILE_POINTER: + case SQLITE_FCNTL_SYNC_OMITTED: + case SQLITE_FCNTL_WIN32_AV_RETRY: + case SQLITE_FCNTL_PERSIST_WAL: + case SQLITE_FCNTL_OVERWRITE: + case SQLITE_FCNTL_POWERSAFE_OVERWRITE: + case SQLITE_FCNTL_PRAGMA: + case SQLITE_FCNTL_BUSYHANDLER: + case SQLITE_FCNTL_MMAP_SIZE: + case SQLITE_FCNTL_TRACE: + case SQLITE_FCNTL_HAS_MOVED: + case SQLITE_FCNTL_SYNC: + case SQLITE_FCNTL_COMMIT_PHASETWO: + case SQLITE_FCNTL_WIN32_SET_HANDLE: + case SQLITE_FCNTL_WAL_BLOCK: + case SQLITE_FCNTL_ZIPVFS: + case SQLITE_FCNTL_RBU: + case SQLITE_FCNTL_VFS_POINTER: + case SQLITE_FCNTL_JOURNAL_POINTER: + case SQLITE_FCNTL_WIN32_GET_HANDLE: + case SQLITE_FCNTL_PDB: +#if SQLITE_VERSION_NUMBER >= 3021000 + case SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: + case SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: + case SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: +#endif +#if SQLITE_VERSION_NUMBER >= 3023000 + case SQLITE_FCNTL_LOCK_TIMEOUT: +#endif +#if SQLITE_VERSION_NUMBER >= 3025000 + case SQLITE_FCNTL_DATA_VERSION: +#endif +#if SQLITE_VERSION_NUMBER >= 3028000 + case SQLITE_FCNTL_SIZE_LIMIT: +#endif +#if SQLITE_VERSION_NUMBER >= 3031000 + case SQLITE_FCNTL_CKPT_DONE: +#endif +#if SQLITE_VERSION_NUMBER >= 3032000 + case SQLITE_FCNTL_RESERVE_BYTES: + case SQLITE_FCNTL_CKPT_START: +#endif +#if SQLITE_VERSION_NUMBER >= 3035000 + case SQLITE_FCNTL_EXTERNAL_READER: +#endif +#if SQLITE_VERSION_NUMBER >= 3036000 + case SQLITE_FCNTL_CKSM_FILE: +#endif +#if SQLITE_VERSION_NUMBER >= 3040000 + case SQLITE_FCNTL_RESET_CACHE: +#endif +#if SQLITE_VERSION_NUMBER >= 3048000 + case SQLITE_FCNTL_NULL_IO: +#endif + return true; + default: + return false; + } +} + +/*[clinic input] +_sqlite3.Connection.file_control as pysqlite_connection_file_control + + op: int + The SQLITE_FCNTL_* constant to invoke. + arg: int + The argument to pass to the operation. + / + name: str = "main" + The database name to operate against. + +Invoke a file control method on the database. + +Opcodes which take non-integer arguments are not supported. +[clinic start generated code]*/ + +static PyObject * +pysqlite_connection_file_control_impl(pysqlite_Connection *self, int op, + int arg, const char *name) +/*[clinic end generated code: output=8a9f04093fc1f59c input=8819ab1022e6a5ee]*/ +{ + if(!is_int_fcntl(op)) { + PyErr_Format(PyExc_ValueError, "unknown file control 'op': %d", op); + return NULL; + } + + int val = arg; + int rc; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + rc = sqlite3_file_control(self->db, name, op, &val); + Py_END_ALLOW_THREADS + + if (rc != SQLITE_OK) { + set_error_from_code(self->state, rc); + return NULL; + } + + return PyLong_FromLong(val); +} + + #ifdef PY_SQLITE_HAVE_SERIALIZE /*[clinic input] _sqlite3.Connection.serialize as serialize @@ -2304,7 +2426,7 @@ deserialize_impl(pysqlite_Connection *self, Py_buffer *data, Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } Py_RETURN_NONE; @@ -2499,7 +2621,7 @@ setconfig_impl(pysqlite_Connection *self, int op, int enable) int actual; int rc = sqlite3_db_config(self->db, op, enable, &actual); if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return NULL; } if (enable != actual) { @@ -2534,7 +2656,7 @@ getconfig_impl(pysqlite_Connection *self, int op) int current; int rc = sqlite3_db_config(self->db, op, -1, ¤t); if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return -1; } return current; @@ -2624,6 +2746,7 @@ static PyMethodDef connection_methods[] = { PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF + PYSQLITE_CONNECTION_FILE_CONTROL_METHODDEF SETLIMIT_METHODDEF GETLIMIT_METHODDEF SERIALIZE_METHODDEF diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c index 02d598040775b0..ad3587d88dd854 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -505,7 +505,7 @@ begin_transaction(pysqlite_Connection *self) Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - (void)_pysqlite_seterror(self->state, self->db); + set_error_from_db(self->state, self->db); return -1; } @@ -715,7 +715,7 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, if (rc != SQLITE_OK) { PyObject *exc = PyErr_GetRaisedException(); sqlite3 *db = sqlite3_db_handle(self->st); - _pysqlite_seterror(state, db); + set_error_from_db(state, db); _PyErr_ChainExceptions1(exc); return; } @@ -764,7 +764,7 @@ bind_parameters(pysqlite_state *state, pysqlite_Statement *self, if (rc != SQLITE_OK) { PyObject *exc = PyErr_GetRaisedException(); sqlite3 *db = sqlite3_db_handle(self->st); - _pysqlite_seterror(state, db); + set_error_from_db(state, db); _PyErr_ChainExceptions1(exc); return; } @@ -896,7 +896,7 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation PyErr_Clear(); } } - _pysqlite_seterror(state, self->connection->db); + set_error_from_db(state, self->connection->db); goto error; } @@ -1087,7 +1087,7 @@ pysqlite_cursor_executescript_impl(pysqlite_Cursor *self, return Py_NewRef((PyObject *)self); error: - _pysqlite_seterror(self->connection->state, db); + set_error_from_db(self->connection->state, db); return NULL; } @@ -1122,8 +1122,7 @@ pysqlite_cursor_iternext(PyObject *op) Py_CLEAR(self->statement); } else if (rc != SQLITE_ROW) { - (void)_pysqlite_seterror(self->connection->state, - self->connection->db); + set_error_from_db(self->connection->state, self->connection->db); (void)stmt_reset(self->statement); Py_CLEAR(self->statement); Py_DECREF(row); diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c index 27e8dab92e0e67..4ec1ba636b729c 100644 --- a/Modules/_sqlite/module.c +++ b/Modules/_sqlite/module.c @@ -512,6 +512,68 @@ add_integer_constants(PyObject *module) { ADD_INT(SQLITE_DBCONFIG_LEGACY_FILE_FORMAT); ADD_INT(SQLITE_DBCONFIG_TRUSTED_SCHEMA); #endif + ADD_INT(SQLITE_FCNTL_LOCKSTATE); + ADD_INT(SQLITE_FCNTL_GET_LOCKPROXYFILE); + ADD_INT(SQLITE_FCNTL_SET_LOCKPROXYFILE); + ADD_INT(SQLITE_FCNTL_LAST_ERRNO); + ADD_INT(SQLITE_FCNTL_SIZE_HINT); + ADD_INT(SQLITE_FCNTL_CHUNK_SIZE); + ADD_INT(SQLITE_FCNTL_FILE_POINTER); + ADD_INT(SQLITE_FCNTL_SYNC_OMITTED); + ADD_INT(SQLITE_FCNTL_WIN32_AV_RETRY); + ADD_INT(SQLITE_FCNTL_PERSIST_WAL); + ADD_INT(SQLITE_FCNTL_OVERWRITE); + ADD_INT(SQLITE_FCNTL_POWERSAFE_OVERWRITE); + ADD_INT(SQLITE_FCNTL_PRAGMA); + ADD_INT(SQLITE_FCNTL_BUSYHANDLER); + ADD_INT(SQLITE_FCNTL_MMAP_SIZE); + ADD_INT(SQLITE_FCNTL_TRACE); + ADD_INT(SQLITE_FCNTL_HAS_MOVED); + ADD_INT(SQLITE_FCNTL_SYNC); + ADD_INT(SQLITE_FCNTL_COMMIT_PHASETWO); + ADD_INT(SQLITE_FCNTL_WIN32_SET_HANDLE); + ADD_INT(SQLITE_FCNTL_WAL_BLOCK); + ADD_INT(SQLITE_FCNTL_ZIPVFS); + ADD_INT(SQLITE_FCNTL_RBU); + ADD_INT(SQLITE_FCNTL_VFS_POINTER); + ADD_INT(SQLITE_FCNTL_JOURNAL_POINTER); + ADD_INT(SQLITE_FCNTL_WIN32_GET_HANDLE); + ADD_INT(SQLITE_FCNTL_PDB); +#if SQLITE_VERSION_NUMBER >= 3021000 + ADD_INT(SQLITE_FCNTL_BEGIN_ATOMIC_WRITE); + ADD_INT(SQLITE_FCNTL_COMMIT_ATOMIC_WRITE); + ADD_INT(SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE); +#endif +#if SQLITE_VERSION_NUMBER >= 3023000 + ADD_INT(SQLITE_FCNTL_LOCK_TIMEOUT); +#endif +#if SQLITE_VERSION_NUMBER >= 3025000 + ADD_INT(SQLITE_FCNTL_DATA_VERSION); +#endif +#if SQLITE_VERSION_NUMBER >= 3028000 + ADD_INT(SQLITE_FCNTL_SIZE_LIMIT); +#endif +#if SQLITE_VERSION_NUMBER >= 3031000 + ADD_INT(SQLITE_FCNTL_CKPT_DONE); +#endif +#if SQLITE_VERSION_NUMBER >= 3032000 + ADD_INT(SQLITE_FCNTL_RESERVE_BYTES); + ADD_INT(SQLITE_FCNTL_CKPT_START); +#endif +#if SQLITE_VERSION_NUMBER >= 3035000 + ADD_INT(SQLITE_FCNTL_EXTERNAL_READER); +#endif +#if SQLITE_VERSION_NUMBER >= 3036000 + ADD_INT(SQLITE_FCNTL_CKSM_FILE); +#endif +#if SQLITE_VERSION_NUMBER >= 3040000 + ADD_INT(SQLITE_FCNTL_RESET_CACHE); +#endif +#if SQLITE_VERSION_NUMBER >= 3048000 + ADD_INT(SQLITE_FCNTL_NULL_IO); +#endif +// When updating this list, also update PYSQLITE_LAST_VALID_FCNTL in module.h +// and is_int_fcntl in connection.c #undef ADD_INT return 0; } diff --git a/Modules/_sqlite/statement.c b/Modules/_sqlite/statement.c index facced0dfbfafd..736e60fd778287 100644 --- a/Modules/_sqlite/statement.c +++ b/Modules/_sqlite/statement.c @@ -62,7 +62,7 @@ pysqlite_statement_create(pysqlite_Connection *connection, PyObject *sql) Py_END_ALLOW_THREADS if (rc != SQLITE_OK) { - _pysqlite_seterror(state, db); + set_error_from_db(state, db); return NULL; } diff --git a/Modules/_sqlite/util.c b/Modules/_sqlite/util.c index b0622e66928f47..103248ff55aa0c 100644 --- a/Modules/_sqlite/util.c +++ b/Modules/_sqlite/util.c @@ -118,18 +118,31 @@ raise_exception(PyObject *type, int errcode, const char *errmsg) Py_XDECREF(exc); } +void +set_error_from_code(pysqlite_state *state, int code) +{ + PyObject *exc_class = get_exception_class(state, code); + if (exc_class == NULL) { + // No new exception need be raised. + return; + } + + const char *errmsg = sqlite3_errstr(code); + assert(errmsg != NULL); + raise_exception(exc_class, code, errmsg); +} + /** * Checks the SQLite error code and sets the appropriate DB-API exception. - * Returns the error code (0 means no error occurred). */ -int -_pysqlite_seterror(pysqlite_state *state, sqlite3 *db) +void +set_error_from_db(pysqlite_state *state, sqlite3 *db) { int errorcode = sqlite3_errcode(db); PyObject *exc_class = get_exception_class(state, errorcode); if (exc_class == NULL) { - // No new exception need be raised; just pass the error code - return errorcode; + // No new exception need be raised. + return; } /* Create and set the exception. */ @@ -137,7 +150,6 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db) // sqlite3_errmsg() always returns an UTF-8 encoded message const char *errmsg = sqlite3_errmsg(db); raise_exception(exc_class, extended_errcode, errmsg); - return extended_errcode; } #ifdef WORDS_BIGENDIAN diff --git a/Modules/_sqlite/util.h b/Modules/_sqlite/util.h index 68b1a8cb67ace3..f8e45baffaefe3 100644 --- a/Modules/_sqlite/util.h +++ b/Modules/_sqlite/util.h @@ -30,9 +30,9 @@ /** * Checks the SQLite error code and sets the appropriate DB-API exception. - * Returns the error code (0 means no error occurred). */ -int _pysqlite_seterror(pysqlite_state *state, sqlite3 *db); +void set_error_from_db(pysqlite_state *state, sqlite3 *db); +void set_error_from_code(pysqlite_state *state, int code); sqlite_int64 _pysqlite_long_as_int64(PyObject * value);