6

I have database with a single table Person which has a name(str) and age(int) columns. So, I create simple validate function for my sqlite3.connection

def adult(age):
    return age > 18

And with following code, it works fine

connection = sqlite3.connect(r'C:\Dev\Garbage\database.db')
with connection:
    connection.create_function('adult', 1, adult)
    cursor = connection.cursor()
    persons = cursor.execute('select "p"."name", "p"."age" from "Person" "p" where adult("p"."age")').fetchall()
    for person in persons:
        print(person)

But if I change adult like this

def adult(age):
    return 1 / 0

I will get sqlite3.OperationalError: user-defined function raised exception. In my project, it might be a huge amount of functions and I'd like to know - is there any way to know which function raised an exception? Or get ZeroDivisionError: division by zero instead of this.

sashaaero
  • 2,618
  • 5
  • 23
  • 41
  • couldn't you use a try except block, and raise the ZeroDivisionError on the except? – Kevin Pasquarella Aug 22 '17 at 18:11
  • @KevinPasquarella it's just an example. I'd like to know about any exceptions that can appear. – sashaaero Aug 22 '17 at 18:13
  • Do you know in advance how you want to handle any arbitrary exception that might be raised? Can you post an actual Traceback? – wwii Aug 22 '17 at 18:35
  • Seems like some combination of the [traceback](https://docs.python.org/3/library/traceback.html), [inspect](https://docs.python.org/3/library/inspect.html) and [logging](https://docs.python.org/3/library/logging.html) modules might help. There are an number of SO Q&A's that may be relevant; like - https://stackoverflow.com/q/1095601/2823755. Or https://stackoverflow.com/q/5067604/2823755 – wwii Aug 22 '17 at 18:48
  • @Yes, I'd like to tell about it to the user. It's complete traceback. I mean, trace stack contains only this call. Thanks for the advice, will try to find a solution. – sashaaero Aug 22 '17 at 18:57

1 Answers1

8

Python's sqlite3 module throws away any error information from the exception, and replaces it with the constant message you've seen:

void _pysqlite_func_callback(sqlite3_context* context, int argc, sqlite3_value** argv)
{
    ...
    py_func = (PyObject*)sqlite3_user_data(context);

    args = _pysqlite_build_py_params(context, argc, argv);
    if (args) {
        py_retval = PyObject_CallObject(py_func, args);
        Py_DECREF(args);
    }

    ok = 0;
    if (py_retval) {
        ok = _pysqlite_set_result(context, py_retval) == 0;
        Py_DECREF(py_retval);
    }
    if (!ok) {
        if (_enable_callback_tracebacks) {
            PyErr_Print();
        } else {
            PyErr_Clear();
        }
        sqlite3_result_error(context, "user-defined function raised exception", -1);
    }
    ...
}

I don't know what prevents it from appending the exception message to the returned error message.

Anyway, it is possible to print out the inner stack trace by calling enable_callback_tracebacks:

import sqlite3

db = sqlite3.connect(':memory:')

def error():
    raise Exception('hello')

db.create_function('error', 0, error)

sqlite3.enable_callback_tracebacks(True)   # <-- !

db.execute('select error()')
Traceback (most recent call last):
  File "<stdin>", line 1, in error
Exception: hello
Traceback (most recent call last):
  File "<stdin>", line 1, in 
sqlite3.OperationalError: user-defined function raised exception
CL.
  • 173,858
  • 17
  • 217
  • 259