mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Improve C-side exception handling from Java-side UDF callbacks.
FossilOrigin-Name: aebbc24afb339ed07b7cd767fbc0d25f3e9c3d9bb5ef3d1c10b04b605c7261bc
This commit is contained in:
@ -1577,21 +1577,46 @@ error_oom:
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
|
||||
static int udf_report_exception(sqlite3_context * cx,
|
||||
const char *zFuncName,
|
||||
const char *zFuncType){
|
||||
int rc;
|
||||
char * z =
|
||||
sqlite3_mprintf("Client-defined function %s.%s() threw. It should "
|
||||
"not do that.",
|
||||
zFuncName ? zFuncName : "<unnamed>", zFuncType);
|
||||
if(z){
|
||||
sqlite3_result_error(cx, z, -1);
|
||||
sqlite3_free(z);
|
||||
rc = SQLITE_ERROR;
|
||||
/*
|
||||
** Must be called immediately after a Java-side UDF callback throws.
|
||||
** If translateToErr is true then it sets the exception's message in
|
||||
** the result error using sqlite3_result_error(). If translateToErr is
|
||||
** false then it emits a warning that the function threw but should
|
||||
** not do so. In either case, it clears the exception state.
|
||||
**
|
||||
** Returns SQLITE_NOMEM if an allocation fails, else SQLITE_ERROR. In
|
||||
** the latter case it calls sqlite3_result_error_nomem().
|
||||
*/
|
||||
static int udf_report_exception(JNIEnv * const env, int translateToErr,
|
||||
sqlite3_context * cx,
|
||||
const char *zFuncName, const char *zFuncType ){
|
||||
jthrowable const ex = (*env)->ExceptionOccurred(env);
|
||||
int rc = SQLITE_ERROR;
|
||||
|
||||
assert(ex && "This must only be called when a Java exception is pending.");
|
||||
if( translateToErr ){
|
||||
char * zMsg;
|
||||
char * z;
|
||||
|
||||
EXCEPTION_CLEAR;
|
||||
zMsg = s3jni_exception_error_msg(env, ex);
|
||||
z = sqlite3_mprintf("Client-defined SQL function %s.%s() threw: %s",
|
||||
zFuncName ? zFuncName : "<unnamed>", zFuncType,
|
||||
zMsg ? zMsg : "Unknown exception" );
|
||||
sqlite3_free(zMsg);
|
||||
if( z ){
|
||||
sqlite3_result_error(cx, z, -1);
|
||||
sqlite3_free(z);
|
||||
}else{
|
||||
sqlite3_result_error_nomem(cx);
|
||||
rc = SQLITE_NOMEM;
|
||||
}
|
||||
}else{
|
||||
sqlite3_result_error_nomem(cx);
|
||||
rc = SQLITE_NOMEM;
|
||||
MARKER(("Client-defined SQL function %s.%s() threw. "
|
||||
"It should not do that.\n",
|
||||
zFuncName ? zFuncName : "<unnamed>", zFuncType));
|
||||
(*env)->ExceptionDescribe( env );
|
||||
EXCEPTION_CLEAR;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -1600,24 +1625,25 @@ static int udf_report_exception(sqlite3_context * cx,
|
||||
Sets up the state for calling a Java-side xFunc/xStep/xInverse()
|
||||
UDF, calls it, and returns 0 on success.
|
||||
*/
|
||||
static int udf_xFSI(sqlite3_context* pCx, int argc,
|
||||
sqlite3_value** argv,
|
||||
S3JniUdf * s,
|
||||
static int udf_xFSI(sqlite3_context* const pCx, int argc,
|
||||
sqlite3_value** const argv,
|
||||
S3JniUdf * const s,
|
||||
jmethodID xMethodID,
|
||||
const char * zFuncType){
|
||||
const char * const zFuncType){
|
||||
LocalJniGetEnv;
|
||||
udf_jargs args = {0,0};
|
||||
int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
|
||||
//MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx));
|
||||
if(rc) return rc;
|
||||
|
||||
//MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
|
||||
if(rc) return rc;
|
||||
if( UDF_SCALAR != s->type ){
|
||||
rc = udf_setAggregateContext(env, args.jcx, pCx, 0);
|
||||
}
|
||||
if( 0 == rc ){
|
||||
(*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
|
||||
IFTHREW{
|
||||
rc = udf_report_exception(pCx, s->zFuncName, zFuncType);
|
||||
rc = udf_report_exception(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
|
||||
s->zFuncName, zFuncType);
|
||||
}
|
||||
}
|
||||
UNREF_L(args.jcx);
|
||||
@ -1635,19 +1661,21 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
|
||||
LocalJniGetEnv;
|
||||
jobject jcx = new_sqlite3_context_wrapper(env, cx);
|
||||
int rc = 0;
|
||||
int const isFinal = 'F'==zFuncType[1]/*xFinal*/;
|
||||
//MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx));
|
||||
if(!jcx){
|
||||
sqlite3_result_error_nomem(cx);
|
||||
if( isFinal ) sqlite3_result_error_nomem(cx);
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
//MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
|
||||
if( UDF_SCALAR != s->type ){
|
||||
rc = udf_setAggregateContext(env, jcx, cx, 1);
|
||||
rc = udf_setAggregateContext(env, jcx, cx, isFinal);
|
||||
}
|
||||
if( 0 == rc ){
|
||||
(*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
|
||||
IFTHREW{
|
||||
rc = udf_report_exception(cx,s->zFuncName, zFuncType);
|
||||
rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
|
||||
zFuncType);
|
||||
}
|
||||
}
|
||||
UNREF_L(jcx);
|
||||
@ -3599,8 +3627,7 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
|
||||
(*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
|
||||
jFXA, jpFts, jpCx, jArgv);
|
||||
IFTHREW{
|
||||
EXCEPTION_CLEAR;
|
||||
udf_report_exception(pCx, pAux->zFuncName, "xFunction");
|
||||
udf_report_exception(env, 1, pCx, pAux->zFuncName, "xFunction");
|
||||
}
|
||||
UNREF_L(jpFts);
|
||||
UNREF_L(jpCx);
|
||||
|
@ -98,7 +98,11 @@ public abstract class SQLFunction {
|
||||
//! Subclass for creating scalar functions.
|
||||
public static abstract class Scalar extends SQLFunction {
|
||||
|
||||
//! As for the xFunc() argument of the C API's sqlite3_create_function()
|
||||
/**
|
||||
As for the xFunc() argument of the C API's
|
||||
sqlite3_create_function(). If this function throws, it is
|
||||
translated into an sqlite3_result_error().
|
||||
*/
|
||||
public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
|
||||
|
||||
/**
|
||||
@ -116,10 +120,18 @@ public abstract class SQLFunction {
|
||||
*/
|
||||
public static abstract class Aggregate<T> extends SQLFunction {
|
||||
|
||||
//! As for the xStep() argument of the C API's sqlite3_create_function()
|
||||
/**
|
||||
As for the xStep() argument of the C API's
|
||||
sqlite3_create_function(). If this function throws, the
|
||||
exception is not propagated and a warning might be emitted to a
|
||||
debugging channel.
|
||||
*/
|
||||
public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
|
||||
|
||||
//! As for the xFinal() argument of the C API's sqlite3_create_function()
|
||||
/**
|
||||
As for the xFinal() argument of the C API's sqlite3_create_function().
|
||||
If this function throws, it is translated into an sqlite3_result_error().
|
||||
*/
|
||||
public abstract void xFinal(sqlite3_context cx);
|
||||
|
||||
/**
|
||||
@ -165,10 +177,18 @@ public abstract class SQLFunction {
|
||||
*/
|
||||
public static abstract class Window<T> extends Aggregate<T> {
|
||||
|
||||
//! As for the xInverse() argument of the C API's sqlite3_create_window_function()
|
||||
/**
|
||||
As for the xInverse() argument of the C API's
|
||||
sqlite3_create_window_function(). If this function throws, the
|
||||
exception is not propagated and a warning might be emitted
|
||||
to a debugging channel.
|
||||
*/
|
||||
public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
|
||||
|
||||
//! As for the xValue() argument of the C API's sqlite3_create_window_function()
|
||||
/**
|
||||
As for the xValue() argument of the C API's sqlite3_create_window_function().
|
||||
See xInverse() for the fate of any exceptions this throws.
|
||||
*/
|
||||
public abstract void xValue(sqlite3_context cx);
|
||||
}
|
||||
}
|
||||
|
@ -158,16 +158,22 @@ public class Tester1 implements Runnable {
|
||||
execSql(db, true, sql);
|
||||
}
|
||||
|
||||
static sqlite3_stmt prepare(sqlite3 db, String sql){
|
||||
static sqlite3_stmt prepare(sqlite3 db, boolean throwOnError, String sql){
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
int rc = sqlite3_prepare(db, sql, outStmt);
|
||||
affirm( 0 == rc );
|
||||
if( throwOnError ){
|
||||
affirm( 0 == rc );
|
||||
}
|
||||
final sqlite3_stmt rv = outStmt.take();
|
||||
affirm( null == outStmt.get() );
|
||||
affirm( 0 != rv.getNativePointer() );
|
||||
if( throwOnError ){
|
||||
affirm( 0 != rv.getNativePointer() );
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static sqlite3_stmt prepare(sqlite3 db, String sql){
|
||||
return prepare(db, true, sql);
|
||||
}
|
||||
private void showCompileOption(){
|
||||
int i = 0;
|
||||
String optName;
|
||||
@ -598,6 +604,47 @@ public class Tester1 implements Runnable {
|
||||
affirm( xDestroyCalled.value );
|
||||
}
|
||||
|
||||
private void testUdfThrows(){
|
||||
final sqlite3 db = createNewDb();
|
||||
final ValueHolder<Integer> xFuncAccum = new ValueHolder<>(0);
|
||||
|
||||
SQLFunction funcAgg = new SQLFunction.Aggregate<Integer>(){
|
||||
@Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
|
||||
/** Throwing from here should emit loud noise on stdout or stderr
|
||||
but the exception is supressed because we have no way to inform
|
||||
sqlite about it from these callbacks. */
|
||||
//throw new RuntimeException("Throwing from an xStep");
|
||||
}
|
||||
@Override public void xFinal(sqlite3_context cx){
|
||||
throw new RuntimeException("Throwing from an xFinal");
|
||||
}
|
||||
};
|
||||
int rc = sqlite3_create_function(db, "myagg", 1, SQLITE_UTF8, funcAgg);
|
||||
affirm(0 == rc);
|
||||
affirm(0 == xFuncAccum.value);
|
||||
sqlite3_stmt stmt = prepare(db, "SELECT myagg(1)");
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm( 0 != rc );
|
||||
affirm( sqlite3_errmsg(db).indexOf("an xFinal") > 0 );
|
||||
|
||||
SQLFunction funcSc = new SQLFunction.Scalar(){
|
||||
@Override public void xFunc(sqlite3_context cx, sqlite3_value[] args){
|
||||
throw new RuntimeException("Throwing from an xFunc");
|
||||
}
|
||||
};
|
||||
rc = sqlite3_create_function(db, "mysca", 0, SQLITE_UTF8, funcSc);
|
||||
affirm(0 == rc);
|
||||
affirm(0 == xFuncAccum.value);
|
||||
stmt = prepare(db, "SELECT mysca()");
|
||||
rc = sqlite3_step(stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
affirm( 0 != rc );
|
||||
affirm( sqlite3_errmsg(db).indexOf("an xFunc") > 0 );
|
||||
|
||||
sqlite3_close_v2(db);
|
||||
}
|
||||
|
||||
private void testUdfJavaObject(){
|
||||
final sqlite3 db = createNewDb();
|
||||
final ValueHolder<sqlite3> testResult = new ValueHolder<>(db);
|
||||
|
Reference in New Issue
Block a user