Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import sqlite3 as sqlite
import subprocess
import sys
import tempfile
import threading
import unittest
import urllib.parse
Expand Down Expand Up @@ -735,6 +736,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.ProgrammingError):
cx.file_control(sqlite.SQLITE_FCNTL_PERSIST_WAL, 1)


class CursorTests(unittest.TestCase):
def setUp(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.sqlite.org/c3ref/file_control.html>`_.
93 changes: 92 additions & 1 deletion Modules/_sqlite/clinic/connection.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 115 additions & 0 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -2173,6 +2173,120 @@ pysqlite_connection_create_collation_impl(pysqlite_Connection *self,
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: long
The argument to pass to the operation.
/
name: str = "main"
The database name to operate against.

Invoke a file control method on the database.
[clinic start generated code]*/

static PyObject *
pysqlite_connection_file_control_impl(pysqlite_Connection *self, int op,
long arg, const char *name)
/*[clinic end generated code: output=ab3230aaca500391 input=506d31506027e9ce]*/
{
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) {
PyErr_SetString(self->ProgrammingError, sqlite3_errstr(rc));
return NULL;
}

return PyLong_FromLong(val);
}


#ifdef PY_SQLITE_HAVE_SERIALIZE
/*[clinic input]
_sqlite3.Connection.serialize as serialize
Expand Down Expand Up @@ -2601,6 +2715,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
Expand Down
64 changes: 64 additions & 0 deletions Modules/_sqlite/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,70 @@ 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_VFSNAME);
ADD_INT(SQLITE_FCNTL_POWERSAFE_OVERWRITE);
ADD_INT(SQLITE_FCNTL_PRAGMA);
ADD_INT(SQLITE_FCNTL_BUSYHANDLER);
ADD_INT(SQLITE_FCNTL_TEMPFILENAME);
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;
}
Expand Down
Loading