mirror of
https://github.com/python/cpython.git
synced 2025-09-27 18:59:43 +00:00
bpo-16379: expose SQLite error codes and error names in sqlite3
(GH-27786)
This commit is contained in:
parent
f62763d267
commit
86d8b46523
8 changed files with 264 additions and 32 deletions
|
@ -24,7 +24,10 @@ while True:
|
||||||
if buffer.lstrip().upper().startswith("SELECT"):
|
if buffer.lstrip().upper().startswith("SELECT"):
|
||||||
print(cur.fetchall())
|
print(cur.fetchall())
|
||||||
except sqlite3.Error as e:
|
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 = ""
|
buffer = ""
|
||||||
|
|
||||||
con.close()
|
con.close()
|
||||||
|
|
|
@ -836,6 +836,20 @@ Exceptions
|
||||||
The base class of the other exceptions in this module. It is a subclass
|
The base class of the other exceptions in this module. It is a subclass
|
||||||
of :exc:`Exception`.
|
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:: DatabaseError
|
||||||
|
|
||||||
Exception raised for errors that are related to the database.
|
Exception raised for errors that are related to the database.
|
||||||
|
|
|
@ -226,6 +226,12 @@ sqlite3
|
||||||
now raise :exc:`UnicodeEncodeError` instead of :exc:`sqlite3.ProgrammingError`.
|
now raise :exc:`UnicodeEncodeError` instead of :exc:`sqlite3.ProgrammingError`.
|
||||||
(Contributed by Erlend E. Aasland in :issue:`44688`.)
|
(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
|
Removed
|
||||||
=======
|
=======
|
||||||
|
|
|
@ -28,12 +28,12 @@ import threading
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from test.support import (
|
from test.support import (
|
||||||
|
SHORT_TIMEOUT,
|
||||||
bigmemtest,
|
bigmemtest,
|
||||||
check_disallow_instantiation,
|
check_disallow_instantiation,
|
||||||
threading_helper,
|
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
|
# Helper for tests using TESTFN
|
||||||
|
@ -102,6 +102,89 @@ class ModuleTests(unittest.TestCase):
|
||||||
sqlite.DatabaseError),
|
sqlite.DatabaseError),
|
||||||
"NotSupportedError is not a subclass of 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
|
# sqlite3_enable_shared_cache() is deprecated on macOS and calling it may raise
|
||||||
# OperationalError on some buildbots.
|
# OperationalError on some buildbots.
|
||||||
@unittest.skipIf(sys.platform == "darwin", "shared cache is deprecated on macOS")
|
@unittest.skipIf(sys.platform == "darwin", "shared cache is deprecated on macOS")
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add SQLite error code and name to :mod:`sqlite3` exceptions.
|
||||||
|
Patch by Aviv Palivoda, Daniel Shahaf, and Erlend E. Aasland.
|
|
@ -282,12 +282,79 @@ static PyMethodDef module_methods[] = {
|
||||||
{NULL, NULL}
|
{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) {
|
static int add_integer_constants(PyObject *module) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
ret += PyModule_AddIntMacro(module, PARSE_DECLTYPES);
|
ret += PyModule_AddIntMacro(module, PARSE_DECLTYPES);
|
||||||
ret += PyModule_AddIntMacro(module, PARSE_COLNAMES);
|
ret += PyModule_AddIntMacro(module, PARSE_COLNAMES);
|
||||||
ret += PyModule_AddIntMacro(module, SQLITE_OK);
|
|
||||||
ret += PyModule_AddIntMacro(module, SQLITE_DENY);
|
ret += PyModule_AddIntMacro(module, SQLITE_DENY);
|
||||||
ret += PyModule_AddIntMacro(module, SQLITE_IGNORE);
|
ret += PyModule_AddIntMacro(module, SQLITE_IGNORE);
|
||||||
ret += PyModule_AddIntMacro(module, SQLITE_CREATE_INDEX);
|
ret += PyModule_AddIntMacro(module, SQLITE_CREATE_INDEX);
|
||||||
|
@ -325,7 +392,6 @@ static int add_integer_constants(PyObject *module) {
|
||||||
#if SQLITE_VERSION_NUMBER >= 3008003
|
#if SQLITE_VERSION_NUMBER >= 3008003
|
||||||
ret += PyModule_AddIntMacro(module, SQLITE_RECURSIVE);
|
ret += PyModule_AddIntMacro(module, SQLITE_RECURSIVE);
|
||||||
#endif
|
#endif
|
||||||
ret += PyModule_AddIntMacro(module, SQLITE_DONE);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,6 +472,11 @@ PyMODINIT_FUNC PyInit__sqlite3(void)
|
||||||
ADD_EXCEPTION(module, state, DataError, state->DatabaseError);
|
ADD_EXCEPTION(module, state, DataError, state->DatabaseError);
|
||||||
ADD_EXCEPTION(module, state, NotSupportedError, state->DatabaseError);
|
ADD_EXCEPTION(module, state, NotSupportedError, state->DatabaseError);
|
||||||
|
|
||||||
|
/* Set error constants */
|
||||||
|
if (add_error_constants(module) < 0) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set integer constants */
|
/* Set integer constants */
|
||||||
if (add_integer_constants(module) < 0) {
|
if (add_integer_constants(module) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
|
|
|
@ -81,6 +81,8 @@ pysqlite_get_state_by_type(PyTypeObject *Py_UNUSED(tp))
|
||||||
return &pysqlite_global_state;
|
return &pysqlite_global_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern const char *pysqlite_error_name(int rc);
|
||||||
|
|
||||||
#define PARSE_DECLTYPES 1
|
#define PARSE_DECLTYPES 1
|
||||||
#define PARSE_COLNAMES 2
|
#define PARSE_COLNAMES 2
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -36,27 +36,19 @@ pysqlite_step(sqlite3_stmt *statement)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Returns non-NULL if a new exception should be raised
|
||||||
* Checks the SQLite error code and sets the appropriate DB-API exception.
|
static PyObject *
|
||||||
* Returns the error code (0 means no error occurred).
|
get_exception_class(pysqlite_state *state, int errorcode)
|
||||||
*/
|
|
||||||
int
|
|
||||||
_pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
|
|
||||||
{
|
{
|
||||||
int errorcode = sqlite3_errcode(db);
|
switch (errorcode) {
|
||||||
|
|
||||||
switch (errorcode)
|
|
||||||
{
|
|
||||||
case SQLITE_OK:
|
case SQLITE_OK:
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
break;
|
return NULL;
|
||||||
case SQLITE_INTERNAL:
|
case SQLITE_INTERNAL:
|
||||||
case SQLITE_NOTFOUND:
|
case SQLITE_NOTFOUND:
|
||||||
PyErr_SetString(state->InternalError, sqlite3_errmsg(db));
|
return state->InternalError;
|
||||||
break;
|
|
||||||
case SQLITE_NOMEM:
|
case SQLITE_NOMEM:
|
||||||
(void)PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
break;
|
|
||||||
case SQLITE_ERROR:
|
case SQLITE_ERROR:
|
||||||
case SQLITE_PERM:
|
case SQLITE_PERM:
|
||||||
case SQLITE_ABORT:
|
case SQLITE_ABORT:
|
||||||
|
@ -70,26 +62,85 @@ _pysqlite_seterror(pysqlite_state *state, sqlite3 *db)
|
||||||
case SQLITE_PROTOCOL:
|
case SQLITE_PROTOCOL:
|
||||||
case SQLITE_EMPTY:
|
case SQLITE_EMPTY:
|
||||||
case SQLITE_SCHEMA:
|
case SQLITE_SCHEMA:
|
||||||
PyErr_SetString(state->OperationalError, sqlite3_errmsg(db));
|
return state->OperationalError;
|
||||||
break;
|
|
||||||
case SQLITE_CORRUPT:
|
case SQLITE_CORRUPT:
|
||||||
PyErr_SetString(state->DatabaseError, sqlite3_errmsg(db));
|
return state->DatabaseError;
|
||||||
break;
|
|
||||||
case SQLITE_TOOBIG:
|
case SQLITE_TOOBIG:
|
||||||
PyErr_SetString(state->DataError, sqlite3_errmsg(db));
|
return state->DataError;
|
||||||
break;
|
|
||||||
case SQLITE_CONSTRAINT:
|
case SQLITE_CONSTRAINT:
|
||||||
case SQLITE_MISMATCH:
|
case SQLITE_MISMATCH:
|
||||||
PyErr_SetString(state->IntegrityError, sqlite3_errmsg(db));
|
return state->IntegrityError;
|
||||||
break;
|
|
||||||
case SQLITE_MISUSE:
|
case SQLITE_MISUSE:
|
||||||
PyErr_SetString(state->ProgrammingError, sqlite3_errmsg(db));
|
return state->ProgrammingError;
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
PyErr_SetString(state->DatabaseError, sqlite3_errmsg(db));
|
return state->DatabaseError;
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return errorcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue