Today we made a couple of minor changes in the code generation to take care of some lingering issues.
The first is that when you did a throw
inside a catch
to basically rethrow the error, you would lose
the error code if something had succeeded within the catch handler.
The old codegen looked something like this:
catch_start_1: {
printf("error\n");
cql_best_error(&_rc_)
goto cql_cleanup;
}
The problem being that while the printf
above is fine and well, if you did any SQL operation then _rc_
would be
clobbered and you'd end up throwing an unrelated error code. cql_best_error
would at least make sure it was
a failure code (SQLITE_ERROR
) but the original error code was lost.
The new code looks like this:
catch_start_1: {
_rc_thrown_ = _rc_;
printf("error\n");
_rc_ = cql_best_error(_rc_thrown_);
goto cql_cleanup;
}
So now if there are db operations, the original return code is still preserved. Note: you still lose sqlite3_errmsg()
because
SQLite doesn't know that cleanup logic is running.
This brings us to the second new thing: general purpose error traces.
Error checking of result codes happens very consistently in CQL output. The usual pattern looks something like this:
_rc_ = cql_exec(_db_,
"SAVEPOINT base_proc_savepoint");
if (_rc_ != SQLITE_OK) goto cql_cleanup;
or if it's inside a try block a little different... very little actually
// try
{
_rc_ = cql_exec(_db_,
"RELEASE SAVEPOINT base_proc_savepoint");
if (_rc_ != SQLITE_OK) goto catch_start_8;
// ... the rest of the try block
}
Basically if the local _rc_
doersn't match the necessary condition we goto
the appropriate error label... either the relevant
catch block or else the procedure's cleanup code.
We generalize this a bit now so that it looks like this:
if (_rc_ != SQLITE_OK) { cql_error_trace(); goto cql_cleanup; }
-- or, in a catch...
if (_rc_ != SQLITE_OK) { cql_error_trace(); goto catch_start_8; }
Now the default implementation of cql_error_trace()
is in cqlrt.h
which you can and should customize. I'll be writing more
about that later but suffice to say you're supposed to replace cqlrt.h
and cqlrt.c
with suitable runtime helpers for your environment
while keeping cqlrt_common.h
and cqlrt_common.c
fixed.
So for instance, your cqlrt.h
could look like this:
#ifndef CQL_TRACING_ENABLED
#define cql_error_trace()
#else
// whatever tracing you want, for example this might help in test code.
#define cql_error_trace() \
fprintf(stderr, "Error at %s:%d in %s: %d %s\n", __FILE__, __LINE__, _PROC_, _rc_, sqlite3_errmsg(_db_))
#endif
So then when you need to debug problems involving lots of error recovery you can watch the entire chain of events easily.
Note that there are some useful variables there:
In any procedure _db_
is the current database and _rc_
is the most recent return code from SQLite. __FILE__
and __LINE__
of course come from the preprocessor. and _PROC_
(one underscore) is now generated by the compiler. Every procedure's
body now begins with:
#undef _PROC_
#define _PROC_ "the_current_procedure"
So by defining your own cql_error_trace macro you can cause whatever logging you need to happen. Note this can be very expensive indeed because this happens a lot and even the string literals needed are a significant cost. So generally this should be off for production builds and enabled as needed for debug builds.
The default implementation is just an empty block
#define cql_error_trace()
But the hook is enough to light up whatever logging you might need, and you can use sqlite3_errmsg()
before that message is gone.
Good hunting.