Skip to content

bpo-43265: Improve sqlite3.Connection.backup error handling #24586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 1 addition & 4 deletions Lib/sqlite3/test/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,7 @@ def test_database_source_name(self):
with self.assertRaises(sqlite.OperationalError) as cm:
with sqlite.connect(':memory:') as bck:
self.cx.backup(bck, name='non-existing')
self.assertIn(
str(cm.exception),
['SQL logic error', 'SQL logic error or missing database']
)
self.assertIn("unknown database", str(cm.exception))

self.cx.execute("ATTACH DATABASE ':memory:' AS attached_db")
self.cx.execute('CREATE TABLE attached_db.foo (key INTEGER)')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improve :meth:`sqlite3.Connection.backup` error handling. The error message
for non-existant target database names is now ``unknown database <database
name>`` instead of ``SQL logic error``. Patch by Erlend E. Aasland.
79 changes: 34 additions & 45 deletions Modules/_sqlite/connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,6 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self,
/*[clinic end generated code: output=306a3e6a38c36334 input=30ae45fc420bfd3b]*/
{
int rc;
int callback_error = 0;
int sleep_ms = (int)(sleep * 1000.0);
sqlite3 *bck_conn;
sqlite3_backup *bck_handle;
Expand Down Expand Up @@ -1648,60 +1647,50 @@ pysqlite_connection_backup_impl(pysqlite_Connection *self,
bck_handle = sqlite3_backup_init(bck_conn, "main", self->db, name);
Py_END_ALLOW_THREADS

if (bck_handle) {
do {
Py_BEGIN_ALLOW_THREADS
rc = sqlite3_backup_step(bck_handle, pages);
Py_END_ALLOW_THREADS
if (bck_handle == NULL) {
_pysqlite_seterror(bck_conn, NULL);
return NULL;
}

if (progress != Py_None) {
PyObject *res;

res = PyObject_CallFunction(progress, "iii", rc,
sqlite3_backup_remaining(bck_handle),
sqlite3_backup_pagecount(bck_handle));
if (res == NULL) {
/* User's callback raised an error: interrupt the loop and
propagate it. */
callback_error = 1;
rc = -1;
} else {
Py_DECREF(res);
}
}
do {
Py_BEGIN_ALLOW_THREADS
rc = sqlite3_backup_step(bck_handle, pages);
Py_END_ALLOW_THREADS

/* Sleep for a while if there are still further pages to copy and
the engine could not make any progress */
if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
if (progress != Py_None) {
int remaining = sqlite3_backup_remaining(bck_handle);
int pagecount = sqlite3_backup_pagecount(bck_handle);
PyObject *res = PyObject_CallFunction(progress, "iii", rc,
remaining, pagecount);
if (res == NULL) {
/* Callback failed: abort backup and bail. */
Py_BEGIN_ALLOW_THREADS
sqlite3_sleep(sleep_ms);
sqlite3_backup_finish(bck_handle);
Py_END_ALLOW_THREADS
return NULL;
}
} while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED);

Py_BEGIN_ALLOW_THREADS
rc = sqlite3_backup_finish(bck_handle);
Py_END_ALLOW_THREADS
} else {
rc = _pysqlite_seterror(bck_conn, NULL);
}
Py_DECREF(res);
}

if (!callback_error && rc != SQLITE_OK) {
/* We cannot use _pysqlite_seterror() here because the backup APIs do
not set the error status on the connection object, but rather on
the backup handle. */
if (rc == SQLITE_NOMEM) {
(void)PyErr_NoMemory();
} else {
PyErr_SetString(pysqlite_OperationalError, sqlite3_errstr(rc));
/* Sleep for a while if there are still further pages to copy and
the engine could not make any progress */
if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
Py_BEGIN_ALLOW_THREADS
sqlite3_sleep(sleep_ms);
Py_END_ALLOW_THREADS
}
}
} while (rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED);

if (!callback_error && rc == SQLITE_OK) {
Py_RETURN_NONE;
} else {
Py_BEGIN_ALLOW_THREADS
rc = sqlite3_backup_finish(bck_handle);
Py_END_ALLOW_THREADS

if (rc != SQLITE_OK) {
_pysqlite_seterror(bck_conn, NULL);
return NULL;
}

Py_RETURN_NONE;
}

/*[clinic input]
Expand Down