Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
83ab267
sqlite3 module expose sqlite error code and name in exceptions
palaviv Apr 13, 2017
eeb10f4
Fix syntax error
palaviv Apr 13, 2017
42636f9
Add NEWS entry
palaviv May 8, 2019
f224223
Update version
palaviv May 8, 2019
31467e3
Fix whitespace
palaviv May 8, 2019
679280d
Merge branch 'main' into sqlite-expose-error-code
Aug 6, 2021
8aa0859
Move exception documentation to the exceptions section
Aug 6, 2021
e4195c0
Use PyModule_AddObjectRef to add error codes
Aug 6, 2021
a4a0762
Refactor: add add_error_constants()
Aug 6, 2021
c18d227
Sort error codes alphabetically
Aug 6, 2021
67e49e8
Normalise style with the rest of the code base (PEP 7, etc.)
Aug 6, 2021
1893ced
Improve "get error name" function
Aug 6, 2021
67ea5f2
Refactor _pysqlite_seterror changes
Aug 6, 2021
6ced38c
Refactor error code table
Aug 6, 2021
467f7ba
Use macro to create error table
Aug 6, 2021
4abd093
Add missing SQLITE_NOTICE and SQLITE_WARNING codes
Aug 6, 2021
70507dd
Add comment
Aug 6, 2021
b617192
Merge branch 'main' into sqlite-expose-error-code
Aug 9, 2021
db053b9
Assert exception class is always set
Aug 9, 2021
98106dd
Use explicit API's iso. Py_BuildValue, etc.
Aug 9, 2021
0567938
Remove duplicate return code entries
Aug 9, 2021
16889a1
Group stuff
Aug 9, 2021
a7a1cf8
Use 'unknown' when a proper SQLite exception cannot be found
Aug 9, 2021
772a29c
Fix complete_statement.py example
Aug 9, 2021
fa5a1af
Improve unit tests
Aug 9, 2021
cf551b1
Adjust NEWS entry and update What's New
Aug 9, 2021
a3646c9
Use PyObject_SetAttrString iso. _PyObject_SetAttrId => slow path/read…
Aug 9, 2021
8a49659
Refactor _pysqlite_seterror
Aug 9, 2021
8d11bc5
Link to sqlite.org/rescode.html iso. sqlite.org/c3ref/c_abort.html
Aug 9, 2021
7a69798
Use test.support.os_helper.temp_dir() in test_error_code_on_exception()
Aug 18, 2021
b9a5a77
Merge branch 'main' into sqlite-expose-error-code
Aug 25, 2021
3589a6a
Address review: document get_exception_class() return value
Aug 25, 2021
a3c45b6
Merge branch 'main' into sqlite-expose-error-code
Aug 25, 2021
5a5683c
Address review: compare char ptr with NULL iso. 0
Aug 26, 2021
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
Next Next commit
sqlite3 module expose sqlite error code and name in exceptions
  • Loading branch information
palaviv committed May 9, 2019
commit 83ab267abe9985664a5989488bd93e09aeefc963
5 changes: 4 additions & 1 deletion Doc/includes/sqlite3/complete_statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
if buffer.lstrip().upper().startswith("SELECT"):
print(cur.fetchall())
except sqlite3.Error as e:
print("An error occurred:", e.args[0])
msg = str(e))
error_code = e.sqlite_errorcode
error_name = e.sqlite_name
print(f"Error {error_name} [Errno {error_code}]: {msg}")
buffer = ""

con.close()
20 changes: 20 additions & 0 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,26 @@ Module functions and constants
disable the feature again.


.. exception:: Error

Raised to signal an error from the underlying SQLite library.

.. attribute:: sqlite_errorcode

The numeric error code from the `SQLite API
<http://www.sqlite.org/c3ref/c_abort.html>`_.

.. versionadded:: 3.7

.. attribute:: sqlite_errorname

The symbolic name of the numeric error code
from the `SQLite API
<http://www.sqlite.org/c3ref/c_abort.html>`_.

.. versionadded:: 3.7


.. _sqlite3-connection-objects:

Connection Objects
Expand Down
8 changes: 8 additions & 0 deletions Lib/sqlite3/test/dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ def CheckNotSupportedError(self):
sqlite.DatabaseError),
"NotSupportedError is not a subclass of DatabaseError")

def CheckErrorCodeOnException(self):
with self.assertRaises(sqlite.Error) as cm:
db = sqlite.connect('/no/such/file/exists')
e = cm.exception
self.assertEqual(e.sqlite_errorcode, sqlite.SQLITE_CANTOPEN)
self.assertEqual(e.sqlite_errorname, "SQLITE_CANTOPEN")
self.assertEqual(str(e), "unable to open database file")

class ConnectionTests(unittest.TestCase):

def setUp(self):
Expand Down
96 changes: 90 additions & 6 deletions Modules/_sqlite/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,70 @@ struct _IntConstantPair {

typedef struct _IntConstantPair IntConstantPair;

/* sqlite API error codes */
static const IntConstantPair _error_codes[] = {
{"SQLITE_OK", SQLITE_OK},
{"SQLITE_ERROR", SQLITE_ERROR},
{"SQLITE_INTERNAL", SQLITE_INTERNAL},
{"SQLITE_PERM", SQLITE_PERM},
{"SQLITE_ABORT", SQLITE_ABORT},
{"SQLITE_BUSY", SQLITE_BUSY},
{"SQLITE_LOCKED", SQLITE_LOCKED},
{"SQLITE_NOMEM", SQLITE_NOMEM},
{"SQLITE_READONLY", SQLITE_READONLY},
{"SQLITE_INTERRUPT", SQLITE_INTERRUPT},
{"SQLITE_IOERR", SQLITE_IOERR},
{"SQLITE_CORRUPT", SQLITE_CORRUPT},
{"SQLITE_NOTFOUND", SQLITE_NOTFOUND},
{"SQLITE_FULL", SQLITE_FULL},
{"SQLITE_CANTOPEN", SQLITE_CANTOPEN},
{"SQLITE_PROTOCOL", SQLITE_PROTOCOL},
{"SQLITE_EMPTY", SQLITE_EMPTY},
{"SQLITE_SCHEMA", SQLITE_SCHEMA},
{"SQLITE_TOOBIG", SQLITE_TOOBIG},
{"SQLITE_CONSTRAINT", SQLITE_CONSTRAINT},
{"SQLITE_MISMATCH", SQLITE_MISMATCH},
{"SQLITE_MISUSE", SQLITE_MISUSE},
#ifdef SQLITE_NOLFS
{"SQLITE_NOLFS", SQLITE_NOLFS},
#endif
#ifdef SQLITE_AUTH
{"SQLITE_AUTH", SQLITE_AUTH},
#endif
#ifdef SQLITE_FORMAT
{"SQLITE_FORMAT", SQLITE_FORMAT},
#endif
#ifdef SQLITE_RANGE
{"SQLITE_RANGE", SQLITE_RANGE},
#endif
#ifdef SQLITE_NOTADB
{"SQLITE_NOTADB", SQLITE_NOTADB},
#endif
{"SQLITE_DONE", SQLITE_DONE},
{"SQLITE_ROW", SQLITE_ROW},
{(char*)NULL, 0},
{"SQLITE_UNKNOWN", -1}
};

const char *sqlite3ErrName(int rc) {
int i;
for (i = 0; _error_codes[i].constant_name != 0; i++) {
if (_error_codes[i].constant_value == rc)
return _error_codes[i].constant_name;
}
// No error code matched.
return _error_codes[i+1].constant_name;
}

static const IntConstantPair _int_constants[] = {
{"PARSE_DECLTYPES", PARSE_DECLTYPES},
{"PARSE_COLNAMES", PARSE_COLNAMES},

{"SQLITE_OK", SQLITE_OK},
/* enumerated return values for sqlite3_set_authorizer() callback */
{"SQLITE_DENY", SQLITE_DENY},
{"SQLITE_IGNORE", SQLITE_IGNORE},

/* enumerated values for sqlite3_set_authorizer() callback */
{"SQLITE_CREATE_INDEX", SQLITE_CREATE_INDEX},
{"SQLITE_CREATE_TABLE", SQLITE_CREATE_TABLE},
{"SQLITE_CREATE_TEMP_INDEX", SQLITE_CREATE_TEMP_INDEX},
Expand Down Expand Up @@ -342,6 +399,29 @@ static struct PyModuleDef _sqlite3module = {
NULL
};


static int add_to_dict(PyObject *dict, const char *key, int value)
{
int sawerror;
PyObject *value_obj = PyLong_FromLong(value);
PyObject *name = PyUnicode_FromString(key);

if (!value_obj || !name) {
Py_XDECREF(name);
Py_XDECREF(value_obj);
return 1;
}

sawerror = PyDict_SetItem(dict, name, value_obj) < 0;

Py_DECREF(value_obj);
Py_DECREF(name);

if (sawerror)
return 1;
return 0;
}

PyMODINIT_FUNC PyInit__sqlite3(void)
{
PyObject *module, *dict;
Expand Down Expand Up @@ -445,12 +525,16 @@ PyMODINIT_FUNC PyInit__sqlite3(void)

/* Set integer constants */
for (i = 0; _int_constants[i].constant_name != NULL; i++) {
tmp_obj = PyLong_FromLong(_int_constants[i].constant_value);
if (!tmp_obj) {
if (add_to_dict(dict, _int_constants[i].constant_name,
_int_constants[i].constant_value) != 0)
goto error;
}

/* Set error constants */
for (i = 0; _error_codes[i].constant_name != 0; i++) {
if (add_to_dict(dict, _error_codes[i].constant_name,
_error_codes[i].constant_value) != 0)
goto error;
}
PyDict_SetItemString(dict, _int_constants[i].constant_name, tmp_obj);
Py_DECREF(tmp_obj);
}

if (!(tmp_obj = PyUnicode_FromString(PYSQLITE_VERSION))) {
Expand Down
2 changes: 2 additions & 0 deletions Modules/_sqlite/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ extern PyObject* _pysqlite_converters;
extern int _pysqlite_enable_callback_tracebacks;
extern int pysqlite_BaseTypeAdapted;

extern const char *sqlite3ErrName(int rc);

#define PARSE_DECLTYPES 1
#define PARSE_COLNAMES 2
#endif
64 changes: 55 additions & 9 deletions Modules/_sqlite/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,21 @@ int pysqlite_step(sqlite3_stmt* statement, pysqlite_Connection* connection)
*/
int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st)
{
PyObject *exc_class;
int errorcode = sqlite3_errcode(db);

switch (errorcode)
{
case SQLITE_OK:
PyErr_Clear();
break;
return errorcode;
case SQLITE_INTERNAL:
case SQLITE_NOTFOUND:
PyErr_SetString(pysqlite_InternalError, sqlite3_errmsg(db));
exc_class = pysqlite_InternalError;
break;
case SQLITE_NOMEM:
(void)PyErr_NoMemory();
break;
return errorcode;
case SQLITE_ERROR:
case SQLITE_PERM:
case SQLITE_ABORT:
Expand All @@ -74,26 +75,71 @@ int _pysqlite_seterror(sqlite3* db, sqlite3_stmt* st)
case SQLITE_PROTOCOL:
case SQLITE_EMPTY:
case SQLITE_SCHEMA:
PyErr_SetString(pysqlite_OperationalError, sqlite3_errmsg(db));
exc_class = pysqlite_OperationalError;
break;
case SQLITE_CORRUPT:
PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db));
exc_class = pysqlite_DatabaseError;
break;
case SQLITE_TOOBIG:
PyErr_SetString(pysqlite_DataError, sqlite3_errmsg(db));
exc_class = pysqlite_DataError;
break;
case SQLITE_CONSTRAINT:
case SQLITE_MISMATCH:
PyErr_SetString(pysqlite_IntegrityError, sqlite3_errmsg(db));
exc_class = pysqlite_IntegrityError;
break;
case SQLITE_MISUSE:
PyErr_SetString(pysqlite_ProgrammingError, sqlite3_errmsg(db));
exc_class = pysqlite_ProgrammingError;
break;
default:
PyErr_SetString(pysqlite_DatabaseError, sqlite3_errmsg(db));
exc_class = pysqlite_DatabaseError;
break;
}

/* Create and set the exception. */
{
const char *error_msg;
const char *error_name;
PyObject *exc = NULL;
PyObject *args = NULL;
PyObject *py_code = NULL;
PyObject *py_name = NULL;

error_name = sqlite3ErrName(errorcode);

error_msg = sqlite3_errmsg(db);

args = Py_BuildValue("(s)", error_msg);
if (!args)
goto error;

exc = PyObject_Call(exc_class, args, NULL);
if (!exc)
goto error;

py_code = Py_BuildValue("i", errorcode);
if (!py_code)
goto error;

if (PyObject_SetAttrString(exc, "sqlite_errorcode", py_code) < 0)
goto error;

py_name = Py_BuildValue("s", error_name);
if (!py_name)
goto error;

if (PyObject_SetAttrString(exc, "sqlite_errorname", py_name) < 0)
goto error;

PyErr_SetObject((PyObject *) Py_TYPE(exc), exc);

error:
Py_XDECREF(py_code);
Py_XDECREF(py_name);
Py_XDECREF(args);
Py_XDECREF(exc);
}


return errorcode;
}

Expand Down