1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-01 06:27:03 +03:00

JNI: change sqlite3_prepare_multi()'s exception-handling semantics to be more C-like and, to support that, add the package-private sqlite3_jni_db_error() method to set the db error state from package-level Java code.

FossilOrigin-Name: 46656b354311ec0a36832af1c4ccb3b6a244aa55cfb3681e25c3f42b13b387dd
This commit is contained in:
stephan
2023-11-14 02:43:30 +00:00
parent b481413d95
commit adcd13d243
8 changed files with 98 additions and 31 deletions

View File

@ -3879,7 +3879,9 @@ S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)(
** any resources owned by that cache entry and making that slot
** available for re-use.
*/
JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
S3JniApi(sqlite3_java_uncache_thread(), jboolean, 1java_1uncache_1thread)(
JniArgsEnvClass
){
int rc;
S3JniEnv_mutex_enter;
rc = S3JniEnv_uncache(env);
@ -3887,7 +3889,25 @@ JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
return rc ? JNI_TRUE : JNI_FALSE;
}
JniDecl(jboolean,1jni_1supports_1nio)(JniArgsEnvClass){
S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)(
JniArgsEnvClass, jobject jDb, jint jRc, jstring jStr
){
S3JniDb * const ps = S3JniDb_from_java(jDb);
int rc = SQLITE_MISUSE;
if( ps ){
char *zStr;
zStr = jStr
? s3jni_jstring_to_utf8( jStr, 0)
: NULL;
rc = s3jni_db_error( ps->pDb, (int)jRc, zStr );
sqlite3_free(zStr);
}
return rc;
}
S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)(
JniArgsEnvClass
){
return SJG.g.cByteBuffer ? JNI_TRUE : JNI_FALSE;
}
@ -4065,10 +4085,11 @@ S3JniApi(sqlite3_open_v2(),jint,1open_1v2)(
}
/* Proxy for the sqlite3_prepare[_v2/3]() family. */
jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self,
jlong jpDb, jbyteArray baSql,
jint nMax, jint prepFlags,
jobject jOutStmt, jobject outTail){
static jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env,
jclass self,
jlong jpDb, jbyteArray baSql,
jint nMax, jint prepFlags,
jobject jOutStmt, jobject outTail){
sqlite3_stmt * pStmt = 0;
jobject jStmt = 0;
const char * zTail = 0;

View File

@ -785,6 +785,14 @@ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio
(JNIEnv *, jclass);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_jni_db_error
* Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1db_1error
(JNIEnv *, jclass, jobject, jint, jstring);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_aggregate_context

View File

@ -131,6 +131,25 @@ public final class CApi {
*/
public static native boolean sqlite3_jni_supports_nio();
/**
For internal use only. Sets the given db's error code and
(optionally) string. If rc is 0, it defaults to SQLITE_ERROR.
On success it returns rc. On error it may return a more serious
code, such as SQLITE_NOMEM. Returns SQLITE_MISUSE if db is null.
*/
static native int sqlite3_jni_db_error(@NotNull sqlite3 db,
int rc, @Nullable String msg);
/**
Convenience overload which uses e.toString() as the error
message.
*/
static int sqlite3_jni_db_error(@NotNull sqlite3 db,
int rc, @NotNull Exception e){
return sqlite3_jni_db_error(db, rc, e.toString());
}
//////////////////////////////////////////////////////////////////////
// Maintenance reminder: please keep the sqlite3_.... functions
// alphabetized. The SQLITE_... values. on the other hand, are
@ -1324,9 +1343,13 @@ public final class CApi {
array. It loops over the input bytes looking for
statements. Each one it finds is passed to p.call(), passing
ownership of it to that function. If p.call() returns 0, looping
continues, else the loop stops.
continues, else the loop stops and p.call()'s result code is
returned. If preparation of any given segment fails, looping
stops and that result code is returned.
<p>If p.call() throws, the exception is propagated.
<p>If p.call() throws, the exception is converted to a db-level
error and a non-0 code is returned, in order to retain the
C-style error semantics of the API.
<p>How each statement is handled, including whether it is finalized
or not, is up to the callback object. e.g. the callback might
@ -1358,7 +1381,11 @@ public final class CApi {
// empty statement (whitespace/comments)
continue;
}
rc = p.call(stmt);
try{
rc = p.call(stmt);
}catch(Exception e){
rc = sqlite3_jni_db_error( db, SQLITE_ERROR, e );
}
}
return rc;
}

View File

@ -25,7 +25,10 @@ public interface PrepareMultiCallback extends CallbackProxy {
sqlite3_prepare_multi() will _not_ finalize st - it is up
to the call() implementation how st is handled.
Must return 0 on success or an SQLITE_... code on error.
Must return 0 on success or an SQLITE_... code on error. If it
throws, sqlite3_prepare_multi() will transform the exception into
a db-level error in order to retain the C-style error semantics
of the API.
See the {@link Finalize} class for a wrapper which finalizes the
statement after calling a proxy PrepareMultiCallback.
@ -37,7 +40,7 @@ public interface PrepareMultiCallback extends CallbackProxy {
any sqlite3_stmt passed to its callback.
*/
public static final class Finalize implements PrepareMultiCallback {
private PrepareMultiCallback p;
private final PrepareMultiCallback p;
/**
p is the proxy to call() when this.call() is called.
*/

View File

@ -1790,9 +1790,13 @@ public class Tester1 implements Runnable {
};
final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
final ValueHolder<String> toss = new ValueHolder<>(null);
PrepareMultiCallback m = new PrepareMultiCallback() {
@Override public int call(sqlite3_stmt st){
liStmt.add(st);
if( null!=toss.value ){
throw new RuntimeException(toss.value);
}
return proxy.call(st);
}
};
@ -1802,6 +1806,10 @@ public class Tester1 implements Runnable {
for( sqlite3_stmt st : liStmt ){
sqlite3_finalize(st);
}
toss.value = "This is an exception.";
rc = sqlite3_prepare_multi(db, "SELECT 1", m);
affirm( SQLITE_ERROR==rc );
affirm( sqlite3_errmsg(db).indexOf(toss.value)>0 );
sqlite3_close_v2(db);
}

View File

@ -605,6 +605,14 @@ public final class Sqlite implements AutoCloseable {
prepareMulti( sql, 0, visitor );
}
/**
Equivallent to prepareMulti(X,prepFlags,visitor), where X is
sql.getBytes(StandardCharsets.UTF_8).
*/
public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){
prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor);
}
/**
A variant of prepare() which can handle multiple SQL statements
in a single input string. For each statement in the given string,
@ -646,14 +654,6 @@ public final class Sqlite implements AutoCloseable {
}
}
/**
Equivallent to prepareMulti(X,prepFlags,visitor), where X is
sql.getBytes(StandardCharsets.UTF_8).
*/
public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){
prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor);
}
public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){
int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
new SqlFunction.ScalarAdapter(f));