memset(p, 0, sizeof(NphCacheLine));
}
-#define S3JNI_ENABLE_AUTOEXT 0
+#define S3JNI_ENABLE_AUTOEXT 1
#if S3JNI_ENABLE_AUTOEXT
/*
Whether auto extensions are feasible here is currently unknown due
typedef struct S3JniAutoExtension S3JniAutoExtension;
typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*);
struct S3JniAutoExtension {
- JNIEnv * env;
jobject jObj;
jmethodID midFunc;
S3JniAutoExtension_xEntryPoint xEntryPoint;
} metrics;
#if S3JNI_ENABLE_AUTOEXT
struct {
- S3JniAutoExtension *pHead;
- int isRunning;
+ S3JniAutoExtension *pHead /* Head of the auto-extension list */;
+ PerDbStateJni * psOpening /* handle to the being-opened db. We
+ need this so that auto extensions
+ can have a consistent view of the
+ cross-language db connection and
+ behave property if they call further
+ db APIs. */;
+ int isRunning /* True while auto extensions are
+ running. This is used to prohibit
+ manipulation of the auto-extension
+ list while extensions are
+ running. */;
} autoExt;
#endif
} S3Global;
return row;
}
+/**
+ Requires jx to be a Throwable. Calls its getMessage() method and
+ returns its value converted to a UTF-8 string. The caller owns the
+ returned string and must eventually sqlite3_free() it. Returns 0
+ if there is a problem fetching the info or on OOM.
+*/
+static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){
+ JNIEnvCache * const jc = S3Global_env_cache(env);
+ jmethodID mid;
+ jstring msg;
+ char * zMsg;
+ jclass const klazz = (*env)->GetObjectClass(env, jx);
+ EXCEPTION_IS_FATAL("Cannot get class of exception object.");
+ mid = (*env)->GetMethodID(env, klazz, "getMessage", "()Ljava/lang/String;");
+ IFTHREW{
+ EXCEPTION_REPORT;
+ EXCEPTION_CLEAR;
+ return 0;
+ }
+ msg = (*env)->CallObjectMethod(env, jx, mid);
+ IFTHREW{
+ EXCEPTION_REPORT;
+ EXCEPTION_CLEAR;
+ return 0;
+ }
+ zMsg = s3jni_jstring_to_utf8(jc, msg, 0);
+ UNREF_L(msg);
+ return zMsg;
+}
+
/**
Removes any Java references from s and clears its state. If
doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's
}
#endif
+#if S3JNI_ENABLE_AUTOEXT
+/**
+ Unlink ax from S3Global.autoExt and free it.
+*/
+static void S3JniAutoExtension_free(JNIEnv * const env,
+ S3JniAutoExtension * const ax){
+ if( ax ){
+ if( ax->pNext ) ax->pNext->pPrev = ax->pPrev;
+ if( ax == S3Global.autoExt.pHead ){
+ assert( !ax->pNext );
+ S3Global.autoExt.pHead = ax->pNext;
+ }else if( ax->pPrev ){
+ ax->pPrev->pNext = ax->pNext;
+ }
+ ax->pNext = ax->pPrev = 0;
+ UNREF_G(ax->jObj);
+ sqlite3_free(ax);
+ }
+}
+
+/**
+ Allocates a new auto extension and plugs it in to S3Global.autoExt.
+ Returns 0 on OOM or if there is an error collecting the required
+ state from jAutoExt (which must be an AutoExtension object).
+*/
+static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env,
+ jobject const jAutoExt){
+ S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax));
+ if( ax ){
+ jclass klazz;
+ memset(ax, 0, sizeof(*ax));
+ klazz = (*env)->GetObjectClass(env, jAutoExt);
+ EXCEPTION_IS_FATAL("Cannot get class of jAutoExt.");
+ if(!klazz){
+ S3JniAutoExtension_free(env, ax);
+ return 0;
+ }
+ ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint",
+ "(Lorg/sqlite/jni/sqlite3;)I");
+ if(!ax->midFunc){
+ MARKER(("Error getting xEntryPoint(sqlite3) from object."));
+ S3JniAutoExtension_free(env, ax);
+ return 0;
+ }
+ ax->jObj = REF_G(jAutoExt);
+ ax->pNext = S3Global.autoExt.pHead;
+ if( ax->pNext ) ax->pNext->pPrev = ax;
+ S3Global.autoExt.pHead = ax;
+ }
+ return ax;
+}
+#endif /* S3JNI_ENABLE_AUTOEXT */
+
/**
Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
This function calls sqlite3_aggregate_context() to allocate a tiny
// except for this macro-generated subset which are kept together here
// at the front...
////////////////////////////////////////////////////////////////////////
-WRAP_INT_DB(1errcode, sqlite3_errcode)
WRAP_INT_DB(1error_1offset, sqlite3_error_offset)
WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode)
WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count)
WRAP_INT_SVALUE(1value_1type, sqlite3_value_type)
#if S3JNI_ENABLE_AUTOEXT
-/* auto-extension is very incomplete */
-/*static*/ int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr,
- const struct sqlite3_api_routines *pThunk){
+/* Central auto-extension handler. */
+static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr,
+ const struct sqlite3_api_routines *ignored){
S3JniAutoExtension const * pAX = S3Global.autoExt.pHead;
- jobject jDb;
int rc;
JNIEnv * env = 0;
- if(S3Global.autoExt.isRunning){
+ PerDbStateJni * const ps = S3Global.autoExt.psOpening;
+
+ S3Global.autoExt.psOpening = 0;
+ if( !pAX ){
+ assert( 0==S3Global.autoExt.isRunning );
+ return 0;
+ }else if( S3Global.autoExt.isRunning ){
*pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while "
"auto-extensions are running.");
return SQLITE_MISUSE;
- }
- if( S3Global.jvm->GetEnv(S3Global.jvm, (void **)&env, JNI_VERSION_1_8) ){
+ }else if(!ps){
+ MARKER(("Internal error: cannot find PerDbStateJni for auto-extension\n"));
+ return SQLITE_ERROR;
+ }else if( (*S3Global.jvm)->GetEnv(S3Global.jvm, (void **)&env, JNI_VERSION_1_8) ){
+ assert(!"Cannot get JNIEnv");
*pzErr = sqlite3_mprintf("Could not get current JNIEnv.");
return SQLITE_ERROR;
}
+ assert( !ps->pDb /* it's still being opened */ );
+ ps->pDb = pDb;
+ assert( ps->jDb );
+ NativePointerHolder_set(env, ps->jDb, pDb, S3ClassNames.sqlite3);
S3Global.autoExt.isRunning = 1;
- jDb = new_sqlite3_wrapper( env, pDb );
- EXCEPTION_IS_FATAL("Cannot create sqlite3 wrapper object.");
- // Now we need PerDbStateJni_for_db(env, jDb, pDb, 1)
- // and rewire sqlite3_open(_v2()) to use OutputPointer.sqlite3
- // so that they can have this same jobject.
for( ; pAX; pAX = pAX->pNext ){
- JNIEnv * const env = pAX->env
- /* ^^^ is this correct, or must we use the JavaVM::GetEnv()'s env
- instead? */;
- rc = (*env)->CallVoidMethod(env, pAX->jObj, pAX->midFunc, jDb);
+ rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb);
IFTHREW {
- *pzErr = sqlite3_mprintf("auto-extension threw. TODO: extract error message.");
- rc = SQLITE_ERROR;
+ jthrowable const ex = (*env)->ExceptionOccurred(env);
+ char * zMsg;
+ EXCEPTION_CLEAR;
+ zMsg = s3jni_exception_error_msg(env, ex);
+ UNREF_L(ex);
+ *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg);
+ sqlite3_free(zMsg);
+ rc = rc ? rc : SQLITE_ERROR;
+ break;
+ }else if( rc ){
break;
}
}
- UNREF_L(jDb);
S3Global.autoExt.isRunning = 0;
return rc;
}
-JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){}
+
+JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){
+ static int once = 0;
+ S3JniAutoExtension * ax;
+
+ if( !jAutoExt ) return SQLITE_MISUSE;
+ else if( 0==once && ++once ){
+ sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension );
+ }
+ ax = S3Global.autoExt.pHead;
+ for( ; ax; ax = ax->pNext ){
+ if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ return 0 /* C API treats this as a no-op. */;
+ }
+ }
+ return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM;
+}
#endif /* S3JNI_ENABLE_AUTOEXT */
JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt,
return SQLITE_MISUSE;
}
+#if S3JNI_ENABLE_AUTOEXT
+JDECL(jboolean,1cancel_1auto_1extension)(JENV_OSELF, jobject jAutoExt){
+ S3JniAutoExtension * ax;;
+ if( S3Global.autoExt.isRunning ) return JNI_FALSE;
+ for( ax = S3Global.autoExt.pHead; ax; ax = ax->pNext ){
+ if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
+ S3JniAutoExtension_free(env, ax);
+ return JNI_TRUE;
+ }
+ }
+ return JNI_FALSE;
+}
+#endif /* S3JNI_ENABLE_AUTOEXT */
+
+
/**
Wrapper for sqlite3_close(_v2)().
*/
return jRv;
}
+JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE;
+}
+
JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){
- return (*env)->NewStringUTF(env, sqlite3_errmsg(PtrGet_sqlite3(jpDb)));
+ sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
+ JNIEnvCache * const jc = pDb ? S3Global_env_cache(env) : 0;
+ return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0;
}
JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){
- return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode));
+ return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
+ /* We know these values to be plain ASCII, so pose no
+ MUTF-8 incompatibility */;
}
JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb,
return SQLITE_NOMEM;
}
*ps = PerDbStateJni_alloc(env, 0, *jDb);
+#if S3JNI_ENABLE_AUTOEXT
+ if(*ps){
+ assert(!S3Global.autoExt.psOpening);
+ S3Global.autoExt.psOpening = *ps;
+ }
+#endif
return *ps ? 0 : SQLITE_NOMEM;
}
*/
static int s3jni_open_post(JNIEnv * const env, PerDbStateJni * ps,
sqlite3 **ppDb, jobject jOut, int theRc){
+#if S3JNI_ENABLE_AUTOEXT
+ assert( S3Global.autoExt.pHead ? 0==S3Global.autoExt.psOpening : 1 );
+ S3Global.autoExt.psOpening = 0;
+#endif
if(*ppDb){
assert(ps->jDb);
+#if S3JNI_ENABLE_AUTOEXT
+ assert( S3Global.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb );
+#endif
ps->pDb = *ppDb;
NativePointerHolder_set(env, ps->jDb, *ppDb, S3ClassNames.sqlite3);
}else{
return rc;
}
+#if S3JNI_ENABLE_AUTOEXT
+JDECL(void,1reset_1auto_1extension)(JENV_CSELF){
+ if(!S3Global.autoExt.isRunning){
+ while( S3Global.autoExt.pHead ){
+ S3JniAutoExtension_free(env, S3Global.autoExt.pHead);
+ }
+ }
+}
+#endif /* S3JNI_ENABLE_AUTOEXT */
+
/* sqlite3_result_text/blob() and friends. */
static void result_blob_text(int asBlob, int as64,
int eTextRep/*only for (asBlob=0)*/,
-C Rework\sthe\ssqlite3_open(_v2)()\sorder\sof\soperations\sso\sthat\spending\sauto-extension\ssupport\scan\sget\sahold\sof\sthe\sopen-time\sJava\sstate\sdespite\sthe\sJava/C\s(sqlite3*)\sbinding\snot\shaving\syet\sbeen\sestablished.
-D 2023-08-06T22:09:09.175
+C Bind\sthe\sauto-extension\sAPIs\sto\sJNI.
+D 2023-08-07T00:06:31.250
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99
F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d
-F ext/jni/src/c/sqlite3-jni.c 21177d7c3492f84aad0666c43ea6d9bc0ebd544f66dcb41219f966b137e86c1b
-F ext/jni/src/c/sqlite3-jni.h 2108bb9434fe873e08d6388bce102a474b5c6b00c2ea47d8aee257aca2de2a67
+F ext/jni/src/c/sqlite3-jni.c 406f3fe36661b3628d108a912246b48cafd7fcc1f8b30a563ce6806addb30d71
+F ext/jni/src/c/sqlite3-jni.h 68d219dd351676e819deb38926ebcee0fda141403ce4efa60c3d8bd77993d220
F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892
+F ext/jni/src/org/sqlite/jni/AutoExtension.java aac84ec21c93306ff5c6ff3b0130de5b76f265dfbc8f7cd158d62cfb8384ff57
F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7
F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3edf79fb7a3cb5eaeeaaa074f51521d6d9a962b0dd1ca88074be4fd075258fd8
-F ext/jni/src/org/sqlite/jni/Tester1.java 7ea111e9d52042889f2360e7addfefed4174c1a17dfe6efccf64aaa9a65749cf
+F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 9af0e0ea79db59d5c4dac13f70031dd5069223d8198f7324f8c1c25e60451e8c
+F ext/jni/src/org/sqlite/jni/Tester1.java 63fc2f58b3a5abdad8bd41ff4a1b2572c24fa9246c17a3fcab07dc6adf06ff35
F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee
F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 644999caff9db79562d45520d94aaa24ee88c65e397b6fb9c20a4f0e7f84e1a5
-R c4d749cda5fec61bfb97a76dd1b71afd
+P 34da294ab558880e81eebd7d261bc590551d5a7d2855e844695cef6394647ea7
+R 8af70828073a693d0f965376a6f73a57
U stephan
-Z cb93209a7d6706bee6dfc5cbdd5b5747
+Z 18db46b387e0d752e217b2db93c72ada
# Remove this line to create a well-formed Fossil manifest.