[Python-checkins] bpo-24139: Add support for SQLite extended result codes (GH-28076)
pablogsal
webhook-mailer at python.org
Tue Nov 2 19:49:47 EDT 2021
https://github.com/python/cpython/commit/456e27ac0ac6bc1cfd6da0191bd7802d8667457b
commit: 456e27ac0ac6bc1cfd6da0191bd7802d8667457b
branch: main
author: Erlend Egeberg Aasland <erlend.aasland at innova.no>
committer: pablogsal <Pablogsal at gmail.com>
date: 2021-11-02T23:49:38Z
summary:
bpo-24139: Add support for SQLite extended result codes (GH-28076)
files:
A Misc/NEWS.d/next/Library/2021-08-30-23-10-48.bpo-24139.e38czf.rst
M Doc/whatsnew/3.11.rst
M Lib/test/test_sqlite3/test_dbapi.py
M Modules/_sqlite/module.c
M Modules/_sqlite/util.c
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 3500b1ba6b1cb..26d0dbb32af0b 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -242,11 +242,12 @@ sqlite3
now raise :exc:`UnicodeEncodeError` instead of :exc:`sqlite3.ProgrammingError`.
(Contributed by Erlend E. Aasland in :issue:`44688`.)
-* :mod:`sqlite3` exceptions now include the SQLite error code as
+* :mod:`sqlite3` exceptions now include the SQLite extended error code as
:attr:`~sqlite3.Error.sqlite_errorcode` and the SQLite error name as
:attr:`~sqlite3.Error.sqlite_errorname`.
(Contributed by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland in
- :issue:`16379`.)
+ :issue:`16379` and :issue:`24139`.)
+
* Add :meth:`~sqlite3.Connection.setlimit` and
:meth:`~sqlite3.Connection.getlimit` to :class:`sqlite3.Connection` for
diff --git a/Lib/test/test_sqlite3/test_dbapi.py b/Lib/test/test_sqlite3/test_dbapi.py
index 34895be018078..0ba313d929830 100644
--- a/Lib/test/test_sqlite3/test_dbapi.py
+++ b/Lib/test/test_sqlite3/test_dbapi.py
@@ -157,6 +157,7 @@ def test_module_constants(self):
"SQLITE_PERM",
"SQLITE_PRAGMA",
"SQLITE_PROTOCOL",
+ "SQLITE_RANGE",
"SQLITE_READ",
"SQLITE_READONLY",
"SQLITE_REINDEX",
@@ -187,18 +188,142 @@ def test_module_constants(self):
if sqlite.sqlite_version_info >= (3, 8, 7):
consts.append("SQLITE_LIMIT_WORKER_THREADS")
consts += ["PARSE_DECLTYPES", "PARSE_COLNAMES"]
+ # Extended result codes
+ consts += [
+ "SQLITE_ABORT_ROLLBACK",
+ "SQLITE_BUSY_RECOVERY",
+ "SQLITE_CANTOPEN_FULLPATH",
+ "SQLITE_CANTOPEN_ISDIR",
+ "SQLITE_CANTOPEN_NOTEMPDIR",
+ "SQLITE_CORRUPT_VTAB",
+ "SQLITE_IOERR_ACCESS",
+ "SQLITE_IOERR_BLOCKED",
+ "SQLITE_IOERR_CHECKRESERVEDLOCK",
+ "SQLITE_IOERR_CLOSE",
+ "SQLITE_IOERR_DELETE",
+ "SQLITE_IOERR_DELETE_NOENT",
+ "SQLITE_IOERR_DIR_CLOSE",
+ "SQLITE_IOERR_DIR_FSYNC",
+ "SQLITE_IOERR_FSTAT",
+ "SQLITE_IOERR_FSYNC",
+ "SQLITE_IOERR_LOCK",
+ "SQLITE_IOERR_NOMEM",
+ "SQLITE_IOERR_RDLOCK",
+ "SQLITE_IOERR_READ",
+ "SQLITE_IOERR_SEEK",
+ "SQLITE_IOERR_SHMLOCK",
+ "SQLITE_IOERR_SHMMAP",
+ "SQLITE_IOERR_SHMOPEN",
+ "SQLITE_IOERR_SHMSIZE",
+ "SQLITE_IOERR_SHORT_READ",
+ "SQLITE_IOERR_TRUNCATE",
+ "SQLITE_IOERR_UNLOCK",
+ "SQLITE_IOERR_WRITE",
+ "SQLITE_LOCKED_SHAREDCACHE",
+ "SQLITE_READONLY_CANTLOCK",
+ "SQLITE_READONLY_RECOVERY",
+ ]
+ if sqlite.version_info >= (3, 7, 16):
+ consts += [
+ "SQLITE_CONSTRAINT_CHECK",
+ "SQLITE_CONSTRAINT_COMMITHOOK",
+ "SQLITE_CONSTRAINT_FOREIGNKEY",
+ "SQLITE_CONSTRAINT_FUNCTION",
+ "SQLITE_CONSTRAINT_NOTNULL",
+ "SQLITE_CONSTRAINT_PRIMARYKEY",
+ "SQLITE_CONSTRAINT_TRIGGER",
+ "SQLITE_CONSTRAINT_UNIQUE",
+ "SQLITE_CONSTRAINT_VTAB",
+ "SQLITE_READONLY_ROLLBACK",
+ ]
+ if sqlite.version_info >= (3, 7, 17):
+ consts += [
+ "SQLITE_IOERR_MMAP",
+ "SQLITE_NOTICE_RECOVER_ROLLBACK",
+ "SQLITE_NOTICE_RECOVER_WAL",
+ ]
+ if sqlite.version_info >= (3, 8, 0):
+ consts += [
+ "SQLITE_BUSY_SNAPSHOT",
+ "SQLITE_IOERR_GETTEMPPATH",
+ "SQLITE_WARNING_AUTOINDEX",
+ ]
+ if sqlite.version_info >= (3, 8, 1):
+ consts += ["SQLITE_CANTOPEN_CONVPATH", "SQLITE_IOERR_CONVPATH"]
+ if sqlite.version_info >= (3, 8, 2):
+ consts.append("SQLITE_CONSTRAINT_ROWID")
+ if sqlite.version_info >= (3, 8, 3):
+ consts.append("SQLITE_READONLY_DBMOVED")
+ if sqlite.version_info >= (3, 8, 7):
+ consts.append("SQLITE_AUTH_USER")
+ if sqlite.version_info >= (3, 9, 0):
+ consts.append("SQLITE_IOERR_VNODE")
+ if sqlite.version_info >= (3, 10, 0):
+ consts.append("SQLITE_IOERR_AUTH")
+ if sqlite.version_info >= (3, 14, 1):
+ consts.append("SQLITE_OK_LOAD_PERMANENTLY")
+ if sqlite.version_info >= (3, 21, 0):
+ consts += [
+ "SQLITE_IOERR_BEGIN_ATOMIC",
+ "SQLITE_IOERR_COMMIT_ATOMIC",
+ "SQLITE_IOERR_ROLLBACK_ATOMIC",
+ ]
+ if sqlite.version_info >= (3, 22, 0):
+ consts += [
+ "SQLITE_ERROR_MISSING_COLLSEQ",
+ "SQLITE_ERROR_RETRY",
+ "SQLITE_READONLY_CANTINIT",
+ "SQLITE_READONLY_DIRECTORY",
+ ]
+ if sqlite.version_info >= (3, 24, 0):
+ consts += ["SQLITE_CORRUPT_SEQUENCE", "SQLITE_LOCKED_VTAB"]
+ if sqlite.version_info >= (3, 25, 0):
+ consts += ["SQLITE_CANTOPEN_DIRTYWAL", "SQLITE_ERROR_SNAPSHOT"]
+ if sqlite.version_info >= (3, 31, 0):
+ consts += [
+ "SQLITE_CANTOPEN_SYMLINK",
+ "SQLITE_CONSTRAINT_PINNED",
+ "SQLITE_OK_SYMLINK",
+ ]
+ if sqlite.version_info >= (3, 32, 0):
+ consts += [
+ "SQLITE_BUSY_TIMEOUT",
+ "SQLITE_CORRUPT_INDEX",
+ "SQLITE_IOERR_DATA",
+ ]
+ if sqlite.version_info >= (3, 34, 0):
+ const.append("SQLITE_IOERR_CORRUPTFS")
for const in consts:
with self.subTest(const=const):
self.assertTrue(hasattr(sqlite, const))
def test_error_code_on_exception(self):
err_msg = "unable to open database file"
+ if sys.platform.startswith("win"):
+ err_code = sqlite.SQLITE_CANTOPEN_ISDIR
+ else:
+ err_code = sqlite.SQLITE_CANTOPEN
+
with temp_dir() as db:
with self.assertRaisesRegex(sqlite.Error, err_msg) as cm:
sqlite.connect(db)
e = cm.exception
- self.assertEqual(e.sqlite_errorcode, sqlite.SQLITE_CANTOPEN)
- self.assertEqual(e.sqlite_errorname, "SQLITE_CANTOPEN")
+ self.assertEqual(e.sqlite_errorcode, err_code)
+ self.assertTrue(e.sqlite_errorname.startswith("SQLITE_CANTOPEN"))
+
+ @unittest.skipIf(sqlite.sqlite_version_info <= (3, 7, 16),
+ "Requires SQLite 3.7.16 or newer")
+ def test_extended_error_code_on_exception(self):
+ with managed_connect(":memory:", in_mem=True) as con:
+ with con:
+ con.execute("create table t(t integer check(t > 0))")
+ errmsg = "CHECK constraint failed"
+ with self.assertRaisesRegex(sqlite.IntegrityError, errmsg) as cm:
+ con.execute("insert into t values(-1)")
+ exc = cm.exception
+ self.assertEqual(exc.sqlite_errorcode,
+ sqlite.SQLITE_CONSTRAINT_CHECK)
+ self.assertEqual(exc.sqlite_errorname, "SQLITE_CONSTRAINT_CHECK")
# sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise
# OperationalError on some buildbots.
diff --git a/Misc/NEWS.d/next/Library/2021-08-30-23-10-48.bpo-24139.e38czf.rst b/Misc/NEWS.d/next/Library/2021-08-30-23-10-48.bpo-24139.e38czf.rst
new file mode 100644
index 0000000000000..b44d0cf6e20e3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-08-30-23-10-48.bpo-24139.e38czf.rst
@@ -0,0 +1,2 @@
+Add support for SQLite extended result codes in :exc:`sqlite3.Error`. Patch
+by Erlend E. Aasland.
diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c
index 65229623b84d6..3bca33b8e8c27 100644
--- a/Modules/_sqlite/module.c
+++ b/Modules/_sqlite/module.c
@@ -280,7 +280,22 @@ static PyMethodDef module_methods[] = {
{NULL, NULL}
};
-/* SQLite API error codes */
+/* SQLite C API result codes. See also:
+ * - https://www.sqlite.org/c3ref/c_abort_rollback.html
+ * - https://sqlite.org/changes.html#version_3_3_8
+ * - https://sqlite.org/changes.html#version_3_7_16
+ * - https://sqlite.org/changes.html#version_3_7_17
+ * - https://sqlite.org/changes.html#version_3_8_0
+ * - https://sqlite.org/changes.html#version_3_8_3
+ * - https://sqlite.org/changes.html#version_3_14
+ *
+ * Note: the SQLite changelogs rarely mention new result codes, so in order to
+ * keep the 'error_codes' table in sync with SQLite, we must manually inspect
+ * sqlite3.h for every release.
+ *
+ * We keep the SQLITE_VERSION_NUMBER checks in order to easily declutter the
+ * code when we adjust the SQLite version requirement.
+ */
static const struct {
const char *name;
long value;
@@ -311,6 +326,7 @@ static const struct {
DECLARE_ERROR_CODE(SQLITE_OK),
DECLARE_ERROR_CODE(SQLITE_PERM),
DECLARE_ERROR_CODE(SQLITE_PROTOCOL),
+ DECLARE_ERROR_CODE(SQLITE_RANGE),
DECLARE_ERROR_CODE(SQLITE_READONLY),
DECLARE_ERROR_CODE(SQLITE_ROW),
DECLARE_ERROR_CODE(SQLITE_SCHEMA),
@@ -318,6 +334,115 @@ static const struct {
#if SQLITE_VERSION_NUMBER >= 3007017
DECLARE_ERROR_CODE(SQLITE_NOTICE),
DECLARE_ERROR_CODE(SQLITE_WARNING),
+#endif
+ // Extended result code list
+ DECLARE_ERROR_CODE(SQLITE_ABORT_ROLLBACK),
+ DECLARE_ERROR_CODE(SQLITE_BUSY_RECOVERY),
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN_FULLPATH),
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN_ISDIR),
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN_NOTEMPDIR),
+ DECLARE_ERROR_CODE(SQLITE_CORRUPT_VTAB),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_ACCESS),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_BLOCKED),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_CHECKRESERVEDLOCK),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_CLOSE),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_DELETE),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_DELETE_NOENT),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_DIR_CLOSE),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_DIR_FSYNC),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_FSTAT),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_FSYNC),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_LOCK),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_NOMEM),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_RDLOCK),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_READ),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_SEEK),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_SHMLOCK),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_SHMMAP),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_SHMOPEN),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_SHMSIZE),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_SHORT_READ),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_TRUNCATE),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_UNLOCK),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_WRITE),
+ DECLARE_ERROR_CODE(SQLITE_LOCKED_SHAREDCACHE),
+ DECLARE_ERROR_CODE(SQLITE_READONLY_CANTLOCK),
+ DECLARE_ERROR_CODE(SQLITE_READONLY_RECOVERY),
+#if SQLITE_VERSION_NUMBER >= 3007016
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_CHECK),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_COMMITHOOK),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_FOREIGNKEY),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_FUNCTION),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_NOTNULL),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_PRIMARYKEY),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_TRIGGER),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_UNIQUE),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_VTAB),
+ DECLARE_ERROR_CODE(SQLITE_READONLY_ROLLBACK),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3007017
+ DECLARE_ERROR_CODE(SQLITE_IOERR_MMAP),
+ DECLARE_ERROR_CODE(SQLITE_NOTICE_RECOVER_ROLLBACK),
+ DECLARE_ERROR_CODE(SQLITE_NOTICE_RECOVER_WAL),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3008000
+ DECLARE_ERROR_CODE(SQLITE_BUSY_SNAPSHOT),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_GETTEMPPATH),
+ DECLARE_ERROR_CODE(SQLITE_WARNING_AUTOINDEX),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3008001
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN_CONVPATH),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_CONVPATH),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3008002
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_ROWID),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3008003
+ DECLARE_ERROR_CODE(SQLITE_READONLY_DBMOVED),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3008007
+ DECLARE_ERROR_CODE(SQLITE_AUTH_USER),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3009000
+ DECLARE_ERROR_CODE(SQLITE_IOERR_VNODE),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3010000
+ DECLARE_ERROR_CODE(SQLITE_IOERR_AUTH),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3014001
+ DECLARE_ERROR_CODE(SQLITE_OK_LOAD_PERMANENTLY),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3021000
+ DECLARE_ERROR_CODE(SQLITE_IOERR_BEGIN_ATOMIC),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_COMMIT_ATOMIC),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_ROLLBACK_ATOMIC),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3022000
+ DECLARE_ERROR_CODE(SQLITE_ERROR_MISSING_COLLSEQ),
+ DECLARE_ERROR_CODE(SQLITE_ERROR_RETRY),
+ DECLARE_ERROR_CODE(SQLITE_READONLY_CANTINIT),
+ DECLARE_ERROR_CODE(SQLITE_READONLY_DIRECTORY),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3024000
+ DECLARE_ERROR_CODE(SQLITE_CORRUPT_SEQUENCE),
+ DECLARE_ERROR_CODE(SQLITE_LOCKED_VTAB),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3025000
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN_DIRTYWAL),
+ DECLARE_ERROR_CODE(SQLITE_ERROR_SNAPSHOT),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3031000
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN_SYMLINK),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT_PINNED),
+ DECLARE_ERROR_CODE(SQLITE_OK_SYMLINK),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3032000
+ DECLARE_ERROR_CODE(SQLITE_BUSY_TIMEOUT),
+ DECLARE_ERROR_CODE(SQLITE_CORRUPT_INDEX),
+ DECLARE_ERROR_CODE(SQLITE_IOERR_DATA),
+#endif
+#if SQLITE_VERSION_NUMBER >= 3034000
+ DECLARE_ERROR_CODE(SQLITE_IOERR_CORRUPTFS),
#endif
#undef DECLARE_ERROR_CODE
{NULL, 0},
diff --git a/Modules/_sqlite/util.c b/Modules/_sqlite/util.c
index cfd189dfc3360..113b581bfac73 100644
--- a/Modules/_sqlite/util.c
+++ b/Modules/_sqlite/util.c
@@ -72,6 +72,8 @@ get_exception_class(pysqlite_state *state, int errorcode)
return state->IntegrityError;
case SQLITE_MISUSE:
return state->ProgrammingError;
+ case SQLITE_RANGE:
+ return state->InterfaceError;
default:
return state->DatabaseError;
}
@@ -139,9 +141,10 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
}
/* Create and set the exception. */
+ int extended_errcode = sqlite3_extended_errcode(db);
const char *errmsg = sqlite3_errmsg(db);
- raise_exception(exc_class, errorcode, errmsg);
- return errorcode;
+ raise_exception(exc_class, extended_errcode, errmsg);
+ return extended_errcode;
}
#ifdef WORDS_BIGENDIAN
More information about the Python-checkins
mailing list