Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-16379: Expose sqlite error code #1108

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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.8

.. 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.8


.. _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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add sqlite error code and name to the exceptions of the sqlite3 module.
Patch by Aviv Palivoda based on work by Daniel Shahaf.
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},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @palaviv, thanks for picking this up. Should SQLITE_NOTICE and SQLITE_WARNING be added to this list?

{"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 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like SQLITE_OK is used in the wild (https://github.com/search?l=Python&p=4&q=SQLITE_OK&type=Code) thus it cannot be removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQLITE_OK is definitely in use. For example, it is used one of the valid return values in authoriser callbacks.

{"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