Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
25 changes: 25 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,30 @@ def test_database_keyword(self):
with contextlib.closing(sqlite.connect(database=":memory:")) as cx:
self.assertEqual(type(cx), sqlite.Connection)

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.set_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 con:
with self.assertRaises(sqlite.ProgrammingError):
cx.set_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 @@
sqlite Connection objects now expose a method set_file_control, which is a thin wrapper for `sqlite3_file_control https://www.sqlite.org/c3ref/file_control.html`_.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://devguide.python.org/documentation/markup/ for help with docs markup.

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.

40 changes: 40 additions & 0 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -2173,6 +2173,45 @@ pysqlite_connection_create_collation_impl(pysqlite_Connection *self,
Py_RETURN_NONE;
}

/*[clinic input]
_sqlite3.Connection.set_file_control as pysqlite_connection_set_file_control

op: int
a SQLITE_FCNTL_ constant
arg: long
argument to pass
/
dbname: str = NULL
database name

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

static PyObject *
pysqlite_connection_set_file_control_impl(pysqlite_Connection *self, int op,
long arg, const char *dbname)
/*[clinic end generated code: output=d9d2d311892893b6 input=0253798d9514fea2]*/
{
int rc;
long val = arg;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all sqlite3_file_control opcodes use the int1 datatype. For example SQLITE_FCNTL_VFSNAME and SQLITE_FCNTL_TEMPFILENAME are char *. Suggesting to limit this (like we do for sqlite3_db_config) to only the supported opcodes, and raise an exception for unsupported opcodes.

In any case, we should use int here.

Suggested change
long val = arg;
int val = arg;

Footnotes

  1. int, not long

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also address the first part of this remark.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops! sorry for making you ask twice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I have also removed the SQLITE_FCNTL_TEMPFILENAME and SQLITE_FCNTL_VFSNAME constants. I hope that's alright.


if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
return NULL;
}

Py_BEGIN_ALLOW_THREADS
rc = sqlite3_file_control(self->db, dbname, 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 +2640,7 @@ static PyMethodDef connection_methods[] = {
PYSQLITE_CONNECTION_SET_AUTHORIZER_METHODDEF
PYSQLITE_CONNECTION_SET_PROGRESS_HANDLER_METHODDEF
PYSQLITE_CONNECTION_SET_TRACE_CALLBACK_METHODDEF
PYSQLITE_CONNECTION_SET_FILE_CONTROL_METHODDEF
SETLIMIT_METHODDEF
GETLIMIT_METHODDEF
SERIALIZE_METHODDEF
Expand Down
63 changes: 63 additions & 0 deletions Modules/_sqlite/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,69 @@ 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

#undef ADD_INT
return 0;
}
Expand Down
Loading