[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