summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErlend Egeberg Aasland <erlend.aasland@innova.no>2021-08-30 18:32:21 (GMT)
committerGitHub <noreply@github.com>2021-08-30 18:32:21 (GMT)
commit86d8b465231473f850cc5e906013ba8581ddb503 (patch)
tree38821067898cf1fb5fec7b0102e08776ce9df3fe
parentf62763d26755260c31c717fb396550e00eb6b2a0 (diff)
downloadcpython-86d8b465231473f850cc5e906013ba8581ddb503.zip
cpython-86d8b465231473f850cc5e906013ba8581ddb503.tar.gz
cpython-86d8b465231473f850cc5e906013ba8581ddb503.tar.bz2
bpo-16379: expose SQLite error codes and error names in `sqlite3` (GH-27786)
-rw-r--r--Doc/includes/sqlite3/complete_statement.py5
-rw-r--r--Doc/library/sqlite3.rst14
-rw-r--r--Doc/whatsnew/3.11.rst6
-rw-r--r--Lib/sqlite3/test/dbapi.py87
-rw-r--r--Misc/NEWS.d/next/Library/2019-05-08-15-14-32.bpo-16379.rN5JVe.rst2
-rw-r--r--Modules/_sqlite/module.c75
-rw-r--r--Modules/_sqlite/module.h2
-rw-r--r--Modules/_sqlite/util.c105
8 files changed, 264 insertions, 32 deletions
diff --git a/Doc/includes/sqlite3/complete_statement.py b/Doc/includes/sqlite3/complete_statement.py
index cd38d73..a5c9479 100644
--- a/Doc/includes/sqlite3/complete_statement.py
+++ b/Doc/includes/sqlite3/complete_statement.py
@@ -24,7 +24,10 @@ while True:
if buffer.lstrip().upper().startswith("SELECT"):
print(cur.fetchall())
except sqlite3.Error as e:
- print("An error occurred:", e.args[0])
+ err_msg = str(e)
+ err_code = e.sqlite_errorcode
+ err_name = e.sqlite_errorname
+ print(f"{err_name} ({err_code}): {err_msg}")
buffer = ""
con.close()
diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst
index 6399bed..7c60188 100644
--- a/Doc/library/sqlite3.rst
+++ b/Doc/library/sqlite3.rst
@@ -836,6 +836,20 @@ Exceptions
The base class of the other exceptions in this module. It is a subclass
of :exc:`Exception`.
+ .. attribute:: sqlite_errorcode
+
+ The numeric error code from the
+ `SQLite API <https://sqlite.org/rescode.html>`_
+
+ .. versionadded:: 3.11
+
+ .. attribute:: sqlite_errorname
+
+ The symbolic name of the numeric error code
+ from the `SQLite API <https://sqlite.org/rescode.html>`_
+
+ .. versionadded:: 3.11
+
.. exception:: DatabaseError
Exception raised for errors that are related to the database.
diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index 306385c..1b736c7 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -226,6 +226,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
+ :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`.)
+
Removed
=======
diff --git a/Lib/sqlite3/test/dbapi.py b/Lib/sqlite3/test/dbapi.py
index aadecad..02e42e8 100644
--- a/Lib/sqlite3/test/dbapi.py
+++ b/Lib/sqlite3/test/dbapi.py
@@ -28,12 +28,12 @@ import threading
import unittest
from test.support import (
+ SHORT_TIMEOUT,
bigmemtest,
check_disallow_instantiation,
threading_helper,
- SHORT_TIMEOUT,
)
-from test.support.os_helper import TESTFN, unlink
+from test.support.os_helper import TESTFN, unlink, temp_dir
# Helper for tests using TESTFN
@@ -102,6 +102,89 @@ class ModuleTests(unittest.TestCase):
sqlite.DatabaseError),
"NotSupportedError is not a subclass of DatabaseError")
+ def test_module_constants(self):
+ consts = [
+ "SQLITE_ABORT",
+ "SQLITE_ALTER_TABLE",
+ "SQLITE_ANALYZE",
+ "SQLITE_ATTACH",
+ "SQLITE_AUTH",
+ "SQLITE_BUSY",
+ "SQLITE_CANTOPEN",
+ "SQLITE_CONSTRAINT",
+ "SQLITE_CORRUPT",
+ "SQLITE_CREATE_INDEX",
+ "SQLITE_CREATE_TABLE",
+ "SQLITE_CREATE_TEMP_INDEX",
+ "SQLITE_CREATE_TEMP_TABLE",
+ "SQLITE_CREATE_TEMP_TRIGGER",
+ "SQLITE_CREATE_TEMP_VIEW",
+ "SQLITE_CREATE_TRIGGER",
+ "SQLITE_CREATE_VIEW",
+ "SQLITE_CREATE_VTABLE",
+ "SQLITE_DELETE",
+ "SQLITE_DENY",
+ "SQLITE_DETACH",
+ "SQLITE_DONE",
+ "SQLITE_DROP_INDEX",
+ "SQLITE_DROP_TABLE",
+ "SQLITE_DROP_TEMP_INDEX",
+ "SQLITE_DROP_TEMP_TABLE",
+ "SQLITE_DROP_TEMP_TRIGGER",
+ "SQLITE_DROP_TEMP_VIEW",
+ "SQLITE_DROP_TRIGGER",
+ "SQLITE_DROP_VIEW",
+ "SQLITE_DROP_VTABLE",
+ "SQLITE_EMPTY",
+ "SQLITE_ERROR",
+ "SQLITE_FORMAT",
+ "SQLITE_FULL",
+ "SQLITE_FUNCTION",
+ "SQLITE_IGNORE",
+ "SQLITE_INSERT",
+ "SQLITE_INTERNAL",
+ "SQLITE_INTERRUPT",
+ "SQLITE_IOERR",
+ "SQLITE_LOCKED",
+ "SQLITE_MISMATCH",
+ "SQLITE_MISUSE",
+ "SQLITE_NOLFS",
+ "SQLITE_NOMEM",
+ "SQLITE_NOTADB",
+ "SQLITE_NOTFOUND",
+ "SQLITE_OK",
+ "SQLITE_PERM",
+ "SQLITE_PRAGMA",
+ "SQLITE_PROTOCOL",
+ "SQLITE_READ",
+ "SQLITE_READONLY",
+ "SQLITE_REINDEX",
+ "SQLITE_ROW",
+ "SQLITE_SAVEPOINT",
+ "SQLITE_SCHEMA",
+ "SQLITE_SELECT",
+ "SQLITE_TOOBIG",
+ "SQLITE_TRANSACTION",
+ "SQLITE_UPDATE",
+ ]
+ if sqlite.version_info >= (3, 7, 17):
+ consts += ["SQLITE_NOTICE", "SQLITE_WARNING"]
+ if sqlite.version_info >= (3, 8, 3):
+ consts.append("SQLITE_RECURSIVE")
+ consts += ["PARSE_DECLTYPES", "PARSE_COLNAMES"]
+ 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"
+ 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")
+
# sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise
# OperationalError on some buildbots.
@unittest.skipIf(sys.platform == "darwin", "shared cache is deprecated on macOS")
diff --git a/Misc/NEWS.d/next/Library/2019-05-08-15-14-32.bpo-16379.rN5JVe.rst b/Misc/NEWS.d/next/Library/2019-05-08-15-14-32.bpo-16379.rN5JVe.rst
new file mode 100644
index 0000000..874a9cf
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-08-15-14-32.bpo-16379.rN5JVe.rst
@@ -0,0 +1,2 @@
+Add SQLite error code and name to :mod:`sqlite3` exceptions.
+Patch by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland.
diff --git a/Modules/_sqlite/module.c b/Modules/_sqlite/module.c
index 993e572..47b1f7a 100644
--- a/Modules/_sqlite/module.c
+++ b/Modules/_sqlite/module.c
@@ -282,12 +282,79 @@ static PyMethodDef module_methods[] = {
{NULL, NULL}
};
+/* SQLite API error codes */
+static const struct {
+ const char *name;
+ long value;
+} error_codes[] = {
+#define DECLARE_ERROR_CODE(code) {#code, code}
+ // Primary result code list
+ DECLARE_ERROR_CODE(SQLITE_ABORT),
+ DECLARE_ERROR_CODE(SQLITE_AUTH),
+ DECLARE_ERROR_CODE(SQLITE_BUSY),
+ DECLARE_ERROR_CODE(SQLITE_CANTOPEN),
+ DECLARE_ERROR_CODE(SQLITE_CONSTRAINT),
+ DECLARE_ERROR_CODE(SQLITE_CORRUPT),
+ DECLARE_ERROR_CODE(SQLITE_DONE),
+ DECLARE_ERROR_CODE(SQLITE_EMPTY),
+ DECLARE_ERROR_CODE(SQLITE_ERROR),
+ DECLARE_ERROR_CODE(SQLITE_FORMAT),
+ DECLARE_ERROR_CODE(SQLITE_FULL),
+ DECLARE_ERROR_CODE(SQLITE_INTERNAL),
+ DECLARE_ERROR_CODE(SQLITE_INTERRUPT),
+ DECLARE_ERROR_CODE(SQLITE_IOERR),
+ DECLARE_ERROR_CODE(SQLITE_LOCKED),
+ DECLARE_ERROR_CODE(SQLITE_MISMATCH),
+ DECLARE_ERROR_CODE(SQLITE_MISUSE),
+ DECLARE_ERROR_CODE(SQLITE_NOLFS),
+ DECLARE_ERROR_CODE(SQLITE_NOMEM),
+ DECLARE_ERROR_CODE(SQLITE_NOTADB),
+ DECLARE_ERROR_CODE(SQLITE_NOTFOUND),
+ DECLARE_ERROR_CODE(SQLITE_OK),
+ DECLARE_ERROR_CODE(SQLITE_PERM),
+ DECLARE_ERROR_CODE(SQLITE_PROTOCOL),
+ DECLARE_ERROR_CODE(SQLITE_READONLY),
+ DECLARE_ERROR_CODE(SQLITE_ROW),
+ DECLARE_ERROR_CODE(SQLITE_SCHEMA),
+ DECLARE_ERROR_CODE(SQLITE_TOOBIG),
+#if SQLITE_VERSION_NUMBER >= 3007017
+ DECLARE_ERROR_CODE(SQLITE_NOTICE),
+ DECLARE_ERROR_CODE(SQLITE_WARNING),
+#endif
+#undef DECLARE_ERROR_CODE
+ {NULL, 0},
+};
+
+static int
+add_error_constants(PyObject *module)
+{
+ for (int i = 0; error_codes[i].name != NULL; i++) {
+ const char *name = error_codes[i].name;
+ const long value = error_codes[i].value;
+ if (PyModule_AddIntConstant(module, name, value) < 0) {
+ return -1;
+ }
+ }
+ return 0;
+}
+
+const char *
+pysqlite_error_name(int rc)
+{
+ for (int i = 0; error_codes[i].name != NULL; i++) {
+ if (error_codes[i].value == rc) {
+ return error_codes[i].name;
+ }
+ }
+ // No error code matched.
+ return NULL;
+}
+
static int add_integer_constants(PyObject *module) {
int ret = 0;
ret += PyModule_AddIntMacro(module, PARSE_DECLTYPES);
ret += PyModule_AddIntMacro(module, PARSE_COLNAMES);
- ret += PyModule_AddIntMacro(module, SQLITE_OK);
ret += PyModule_AddIntMacro(module, SQLITE_DENY);
ret += PyModule_AddIntMacro(module, SQLITE_IGNORE);
ret += PyModule_AddIntMacro(module, SQLITE_CREATE_INDEX);
@@ -325,7 +392,6 @@ static int add_integer_constants(PyObject *module) {
#if SQLITE_VERSION_NUMBER >= 3008003
ret += PyModule_AddIntMacro(module, SQLITE_RECURSIVE);
#endif
- ret += PyModule_AddIntMacro(module, SQLITE_DONE);
return ret;
}
@@ -406,6 +472,11 @@ PyMODINIT_FUNC PyInit__sqlite3(void)
ADD_EXCEPTION(module, state, DataError, state->DatabaseError);
ADD_EXCEPTION(module, state, NotSupportedError, state->DatabaseError);
+ /* Set error constants */
+ if (add_error_constants(module) < 0) {
+ goto error;
+ }
+
/* Set integer constants */
if (add_integer_constants(module) < 0) {
goto error;
diff --git a/Modules/_sqlite/module.h b/Modules/_sqlite/module.h
index a286739..c273c1f 100644
--- a/Modules/_sqlite/module.h
+++ b/Modules/_sqlite/module.h
@@ -81,6 +81,8 @@ pysqlite_get_state_by_type(PyTypeObject *Py_UNUSED(tp))
return &pysqlite_global_state;
}
+extern const char *pysqlite_error_name(int rc);
+
#define PARSE_DECLTYPES 1
#define PARSE_COLNAMES 2
#endif
diff --git a/Modules/_sqlite/util.c b/Modules/_sqlite/util.c
index 24cefc6..cfd189d 100644
--- a/Modules/_sqlite/util.c
+++ b/Modules/_sqlite/util.c
@@ -36,27 +36,19 @@ pysqlite_step(sqlite3_stmt *statement)
return rc;
}
-/**
- * Checks the SQLite error code and sets the appropriate DB-API exception.
- * Returns the error code (0 means no error occurred).
- */
-int
-_pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
+// Returns non-NULL if a new exception should be raised
+static PyObject *
+get_exception_class(pysqlite_state *state, int errorcode)
{
- int errorcode = sqlite3_errcode(db);
-
- switch (errorcode)
- {
+ switch (errorcode) {
case SQLITE_OK:
PyErr_Clear();
- break;
+ return NULL;
case SQLITE_INTERNAL:
case SQLITE_NOTFOUND:
- PyErr_SetString(state->InternalError, sqlite3_errmsg(db));
- break;
+ return state->InternalError;
case SQLITE_NOMEM:
- (void)PyErr_NoMemory();
- break;
+ return PyErr_NoMemory();
case SQLITE_ERROR:
case SQLITE_PERM:
case SQLITE_ABORT:
@@ -70,26 +62,85 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
case SQLITE_PROTOCOL:
case SQLITE_EMPTY:
case SQLITE_SCHEMA:
- PyErr_SetString(state->OperationalError, sqlite3_errmsg(db));
- break;
+ return state->OperationalError;
case SQLITE_CORRUPT:
- PyErr_SetString(state->DatabaseError, sqlite3_errmsg(db));
- break;
+ return state->DatabaseError;
case SQLITE_TOOBIG:
- PyErr_SetString(state->DataError, sqlite3_errmsg(db));
- break;
+ return state->DataError;
case SQLITE_CONSTRAINT:
case SQLITE_MISMATCH:
- PyErr_SetString(state->IntegrityError, sqlite3_errmsg(db));
- break;
+ return state->IntegrityError;
case SQLITE_MISUSE:
- PyErr_SetString(state->ProgrammingError, sqlite3_errmsg(db));
- break;
+ return state->ProgrammingError;
default:
- PyErr_SetString(state->DatabaseError, sqlite3_errmsg(db));
- break;
+ return state->DatabaseError;
+ }
+}
+
+static void
+raise_exception(PyObject *type, int errcode, const char *errmsg)
+{
+ PyObject *exc = NULL;
+ PyObject *args[] = { PyUnicode_FromString(errmsg), };
+ if (args[0] == NULL) {
+ goto exit;
+ }
+ exc = PyObject_Vectorcall(type, args, 1, NULL);
+ Py_DECREF(args[0]);
+ if (exc == NULL) {
+ goto exit;
+ }
+
+ PyObject *code = PyLong_FromLong(errcode);
+ if (code == NULL) {
+ goto exit;
+ }
+ int rc = PyObject_SetAttrString(exc, "sqlite_errorcode", code);
+ Py_DECREF(code);
+ if (rc < 0) {
+ goto exit;
+ }
+
+ const char *error_name = pysqlite_error_name(errcode);
+ PyObject *name;
+ if (error_name) {
+ name = PyUnicode_FromString(error_name);
+ }
+ else {
+ name = PyUnicode_InternFromString("unknown");
+ }
+ if (name == NULL) {
+ goto exit;
+ }
+ rc = PyObject_SetAttrString(exc, "sqlite_errorname", name);
+ Py_DECREF(name);
+ if (rc < 0) {
+ goto exit;
+ }
+
+ PyErr_SetObject(type, exc);
+
+exit:
+ Py_XDECREF(exc);
+}
+
+/**
+ * Checks the SQLite error code and sets the appropriate DB-API exception.
+ * Returns the error code (0 means no error occurred).
+ */
+int
+_pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
+{
+ int errorcode = sqlite3_errcode(db);
+ PyObject *exc_class = get_exception_class(state, errorcode);
+ if (exc_class == NULL) {
+ // No new exception need be raised; just pass the error code
+ return errorcode;
}
+ /* Create and set the exception. */
+ const char *errmsg = sqlite3_errmsg(db);
+ raise_exception(exc_class, errorcode, errmsg);
return errorcode;
}