mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
JNI internal cleanups and correct two leaked db handles in test code.
FossilOrigin-Name: f927a30b5bba35991f472084ebaf02779e84c343a4e84f0efb3df7679ff212f8
This commit is contained in:
@ -348,15 +348,15 @@ enum {
|
||||
typedef struct S3JniNphClass S3JniNphClass;
|
||||
struct S3JniNphClass {
|
||||
volatile const S3NphRef * pRef /* Entry from S3NphRefs. */;
|
||||
jclass klazz /* global ref to the concrete
|
||||
NativePointerHolder subclass represented by
|
||||
zClassName */;
|
||||
jclass klazz /* global ref to the concrete
|
||||
** NativePointerHolder subclass represented by
|
||||
** zClassName */;
|
||||
volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
|
||||
new_NativePointerHolder_object(). */;
|
||||
** new_NativePointerHolder_object(). */;
|
||||
volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
|
||||
OutputPointer.T.value */;
|
||||
** OutputPointer.T.value */;
|
||||
volatile jfieldID fidAggCtx /* sqlite3_context::aggregateContext. Used only
|
||||
by the sqlite3_context binding. */;
|
||||
** by the sqlite3_context binding. */;
|
||||
};
|
||||
|
||||
/** State for various hook callbacks. */
|
||||
@ -364,13 +364,13 @@ typedef struct S3JniHook S3JniHook;
|
||||
struct S3JniHook{
|
||||
jobject jObj /* global ref to Java instance */;
|
||||
jmethodID midCallback /* callback method. Signature depends on
|
||||
jObj's type */;
|
||||
** jObj's type */;
|
||||
jclass klazz /* global ref to jObj's class. Only needed
|
||||
by hooks which have an xDestroy() method.
|
||||
We can probably eliminate this and simply
|
||||
do the class lookup at the same
|
||||
(deferred) time we do the xDestroy()
|
||||
lookup. */;
|
||||
** by hooks which have an xDestroy() method.
|
||||
** We can probably eliminate this and simply
|
||||
** do the class lookup at the same
|
||||
** (deferred) time we do the xDestroy()
|
||||
** lookup. */;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -380,7 +380,10 @@ struct S3JniHook{
|
||||
*/
|
||||
typedef struct S3JniDb S3JniDb;
|
||||
struct S3JniDb {
|
||||
JNIEnv *env /* The associated JNIEnv handle */;
|
||||
JNIEnv *env /* Used for cleaning up all dbs owned by a given
|
||||
** thread, noting that this ownership is an artificial
|
||||
** one imposed by our threading constraints, not by
|
||||
** the core library. */;
|
||||
sqlite3 *pDb /* The associated db handle */;
|
||||
jobject jDb /* A global ref of the output object which gets
|
||||
returned from sqlite3_open(_v2)(). We need this in
|
||||
@ -528,6 +531,8 @@ struct S3JniGlobalType {
|
||||
volatile unsigned nMutexPerDb /* number of times perDb.mutex was entered */;
|
||||
volatile unsigned nMutexAutoExt /* number of times autoExt.mutex was entered */;
|
||||
volatile unsigned nDestroy /* xDestroy() calls across all types */;
|
||||
volatile unsigned nPdbAlloc /* Number of S3JniDb alloced. */;
|
||||
volatile unsigned nPdbRecycled /* Number of S3JniDb reused. */;
|
||||
struct {
|
||||
/* Number of calls for each type of UDF callback. */
|
||||
volatile unsigned nFunc;
|
||||
@ -689,6 +694,16 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char *
|
||||
}
|
||||
return jba;
|
||||
}
|
||||
static JNIEnv * s3jni_get_env(void){
|
||||
JNIEnv * env = 0;
|
||||
if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
|
||||
JNI_VERSION_1_8) ){
|
||||
fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
|
||||
abort();
|
||||
}
|
||||
return env;
|
||||
}
|
||||
#define LocalJniGetEnv JNIEnv * const env = s3jni_get_env()
|
||||
|
||||
/**
|
||||
Uses the java.lang.String(byte[],Charset) constructor to create a
|
||||
@ -705,7 +720,7 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char *
|
||||
static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc,
|
||||
const char * const z, int n){
|
||||
jstring rv = NULL;
|
||||
JNIEnv * const env = jc->env;
|
||||
LocalJniGetEnv;
|
||||
if( 0==n || (n<0 && z && !z[0]) ){
|
||||
/* Fast-track the empty-string case via the MUTF-8 API. We could
|
||||
hypothetically do this for any strings where n<4 and z is
|
||||
@ -741,7 +756,7 @@ static jstring s3jni_utf8_to_jstring(S3JniEnv * const jc,
|
||||
*/
|
||||
static char * s3jni_jstring_to_utf8(S3JniEnv * const jc,
|
||||
jstring jstr, int *nLen){
|
||||
JNIEnv * const env = jc->env;
|
||||
LocalJniGetEnv;
|
||||
jbyteArray jba;
|
||||
jsize nBa;
|
||||
char *rv;
|
||||
@ -893,7 +908,7 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest
|
||||
*/
|
||||
static void S3JniDb_set_aside(S3JniDb * const s){
|
||||
if(s){
|
||||
JNIEnv * const env = s->env;
|
||||
LocalJniGetEnv;
|
||||
MUTEX_PDB_ASSERT_LOCKED;
|
||||
//MARKER(("state@%p for db@%p setting aside\n", s, s->pDb));
|
||||
assert(s->pPrev != s);
|
||||
@ -1087,8 +1102,6 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
|
||||
MUTEX_PDB_ENTER;
|
||||
if(SJG.perDb.aFree){
|
||||
rv = SJG.perDb.aFree;
|
||||
//MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb));
|
||||
//MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext));
|
||||
SJG.perDb.aFree = rv->pNext;
|
||||
assert(rv->pNext != rv);
|
||||
assert(rv->pPrev != rv);
|
||||
@ -1099,11 +1112,13 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
|
||||
rv->pNext->pPrev = 0;
|
||||
rv->pNext = 0;
|
||||
}
|
||||
++SJG.metrics.nPdbRecycled;
|
||||
}else{
|
||||
rv = s3jni_malloc(env, sizeof(S3JniDb));
|
||||
//MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb));
|
||||
if(rv){
|
||||
memset(rv, 0, sizeof(S3JniDb));
|
||||
++SJG.metrics.nPdbAlloc;
|
||||
}
|
||||
}
|
||||
if(rv){
|
||||
@ -1357,7 +1372,7 @@ static int encodingTypeIsValid(int eTextRep){
|
||||
static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
|
||||
int nRhs, const void *rhs){
|
||||
S3JniDb * const ps = pArg;
|
||||
JNIEnv * env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
jint rc = 0;
|
||||
jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs);
|
||||
jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL;
|
||||
@ -1380,56 +1395,17 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
|
||||
/* Collation finalizer for use by the sqlite3 internals. */
|
||||
static void CollationState_xDestroy(void *pArg){
|
||||
S3JniDb * const ps = pArg;
|
||||
S3JniHook_unref( ps->env, &ps->collation, 1 );
|
||||
S3JniHook_unref( s3jni_get_env(), &ps->collation, 1 );
|
||||
}
|
||||
|
||||
/* State for sqlite3_result_java_object() and
|
||||
sqlite3_value_java_object(). */
|
||||
/*
|
||||
** State for sqlite3_result_java_object() and
|
||||
** sqlite3_value_java_object().
|
||||
**
|
||||
** TODO: this middle-man struct is no longer necessary. Conider
|
||||
** removing it and passing around jObj itself instead.
|
||||
*/
|
||||
typedef struct {
|
||||
/* The JNI docs say:
|
||||
|
||||
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html
|
||||
|
||||
> The VM is guaranteed to pass the same interface pointer to a
|
||||
native method when it makes multiple calls to the native method
|
||||
from the same Java thread.
|
||||
|
||||
Per the accompanying diagram, the "interface pointer" is the
|
||||
pointer-to-pointer which is passed to all JNI calls
|
||||
(`JNIEnv *env`), implying that we need to be caching that. The
|
||||
verbiage "interface pointer" implies, however, that we should be
|
||||
storing the dereferenced `(*env)` pointer.
|
||||
|
||||
This posts claims it's unsafe to cache JNIEnv at all, even when
|
||||
it's always used in the same thread:
|
||||
|
||||
https://stackoverflow.com/questions/12420463
|
||||
|
||||
And this one seems to contradict that:
|
||||
|
||||
https://stackoverflow.com/questions/13964608
|
||||
|
||||
For later reference:
|
||||
|
||||
https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242
|
||||
|
||||
https://developer.android.com/training/articles/perf-jni
|
||||
|
||||
The later has the following say about caching:
|
||||
|
||||
> If performance is important, it's useful to look the
|
||||
[class/method ID] values up once and cache the results in your
|
||||
native code. Because there is a limit of one JavaVM per
|
||||
process, it's reasonable to store this data in a static local
|
||||
structure. ... The class references, field IDs, and method IDs
|
||||
are guaranteed valid until the class is unloaded. Classes are
|
||||
only unloaded if all classes associated with a ClassLoader can
|
||||
be garbage collected, which is rare but will not be impossible
|
||||
in Android. Note however that the jclass is a class reference
|
||||
and must be protected with a call to NewGlobalRef (see the next
|
||||
section).
|
||||
*/
|
||||
JNIEnv * env;
|
||||
jobject jObj;
|
||||
} ResultJavaVal;
|
||||
|
||||
@ -1439,7 +1415,6 @@ typedef struct {
|
||||
static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){
|
||||
ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal));
|
||||
if(rv){
|
||||
rv->env = env;
|
||||
rv->jObj = jObj ? REF_G(jObj) : 0;
|
||||
}
|
||||
return rv;
|
||||
@ -1448,7 +1423,8 @@ static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){
|
||||
static void ResultJavaVal_finalizer(void *v){
|
||||
if(v){
|
||||
ResultJavaVal * const rv = (ResultJavaVal*)v;
|
||||
if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj);
|
||||
LocalJniGetEnv;
|
||||
UNREF_G(rv->jObj);
|
||||
sqlite3_free(rv);
|
||||
}
|
||||
}
|
||||
@ -1516,7 +1492,6 @@ typedef void (*udf_xFinal_f)(sqlite3_context*);
|
||||
*/
|
||||
typedef struct S3JniUdf S3JniUdf;
|
||||
struct S3JniUdf {
|
||||
JNIEnv * env; /* env registered from */;
|
||||
jobject jObj /* SQLFunction instance */;
|
||||
jclass klazz /* jObj's class */;
|
||||
char * zFuncName /* Only for error reporting and debug logging */;
|
||||
@ -1537,7 +1512,6 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
|
||||
const char * zFV = /* signature for xFinal, xValue */
|
||||
"(Lorg/sqlite/jni/sqlite3_context;)V";
|
||||
memset(s, 0, sizeof(S3JniUdf));
|
||||
s->env = env;
|
||||
s->jObj = REF_G(jObj);
|
||||
s->klazz = REF_G((*env)->GetObjectClass(env, jObj));
|
||||
#define FGET(FuncName,FuncType,Field) \
|
||||
@ -1560,7 +1534,7 @@ static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
|
||||
}
|
||||
|
||||
static void S3JniUdf_free(S3JniUdf * s){
|
||||
JNIEnv * const env = s->env;
|
||||
LocalJniGetEnv;
|
||||
if(env){
|
||||
//MARKER(("UDF cleanup: %s\n", s->zFuncName));
|
||||
s3jni_call_xDestroy(env, s->jObj, s->klazz);
|
||||
@ -1589,10 +1563,6 @@ typedef struct {
|
||||
Converts the given (cx, argc, argv) into arguments for the given
|
||||
UDF, placing the result in the final argument. Returns 0 on
|
||||
success, SQLITE_NOMEM on allocation error.
|
||||
|
||||
TODO: see what we can do to optimize the
|
||||
new_sqlite3_value_wrapper() call. e.g. find the ctor a single time
|
||||
and call it here, rather than looking it up repeatedly.
|
||||
*/
|
||||
static int udf_args(JNIEnv *env,
|
||||
sqlite3_context * const cx,
|
||||
@ -1652,9 +1622,9 @@ static int udf_xFSI(sqlite3_context* pCx, int argc,
|
||||
S3JniUdf * s,
|
||||
jmethodID xMethodID,
|
||||
const char * zFuncType){
|
||||
JNIEnv * const env = s->env;
|
||||
LocalJniGetEnv;
|
||||
udf_jargs args = {0,0};
|
||||
int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv);
|
||||
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));
|
||||
@ -1679,8 +1649,8 @@ static int udf_xFSI(sqlite3_context* pCx, int argc,
|
||||
static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
|
||||
jmethodID xMethodID,
|
||||
const char *zFuncType){
|
||||
JNIEnv * const env = s->env;
|
||||
jobject jcx = new_sqlite3_context_wrapper(s->env, cx);
|
||||
LocalJniGetEnv;
|
||||
jobject jcx = new_sqlite3_context_wrapper(env, cx);
|
||||
int rc = 0;
|
||||
//MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx));
|
||||
if(!jcx){
|
||||
@ -1768,16 +1738,6 @@ WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type)
|
||||
WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype)
|
||||
WRAP_INT_SVALUE(1value_1type, sqlite3_value_type)
|
||||
|
||||
static JNIEnv * s3jni_get_env(void){
|
||||
JNIEnv * env = 0;
|
||||
if( (*SJG.jvm)->GetEnv(SJG.jvm, (void **)&env,
|
||||
JNI_VERSION_1_8) ){
|
||||
fprintf(stderr, "Fatal error: cannot get current JNIEnv.\n");
|
||||
abort();
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
/* Central auto-extension handler. */
|
||||
static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
|
||||
const struct sqlite3_api_routines *ignored){
|
||||
@ -1958,7 +1918,7 @@ static int s3jni_busy_handler(void* pState, int n){
|
||||
S3JniDb * const ps = (S3JniDb *)pState;
|
||||
int rc = 0;
|
||||
if( ps->busyHandler.jObj ){
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj,
|
||||
ps->busyHandler.midCallback, (jint)n);
|
||||
IFTHREW{
|
||||
@ -2079,7 +2039,7 @@ static unsigned int s3jni_utf16_strlen(void const * z){
|
||||
static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
|
||||
int eTextRep, const void * z16Name){
|
||||
S3JniDb * const ps = pState;
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
unsigned int const nName = s3jni_utf16_strlen(z16Name);
|
||||
jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
|
||||
IFTHREW{
|
||||
@ -2184,7 +2144,7 @@ JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt,
|
||||
|
||||
|
||||
static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
int rc = isCommit
|
||||
? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj,
|
||||
ps->commitHook.midCallback)
|
||||
@ -2749,7 +2709,7 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra
|
||||
|
||||
static int s3jni_progress_handler_impl(void *pP){
|
||||
S3JniDb * const ps = (S3JniDb *)pP;
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj,
|
||||
ps->progress.midCallback);
|
||||
IFTHREW{
|
||||
@ -3001,7 +2961,7 @@ JDECL(jobject,1rollback_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
|
||||
static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
|
||||
const char*z2,const char*z3){
|
||||
S3JniDb * const ps = pState;
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
|
||||
S3JniHook const * const pHook = &ps->authHook;
|
||||
jstring const s0 = z0 ? s3jni_utf8_to_jstring(jc, z0, -1) : 0;
|
||||
@ -3119,7 +3079,7 @@ JDECL(jint,1shutdown)(JENV_CSELF){
|
||||
s3jni_reset_auto_extension(env);
|
||||
MUTEX_ENV_ENTER;
|
||||
while( SJG.envCache.aHead ){
|
||||
S3JniGlobal_env_uncache( SJG.envCache.aHead->env );
|
||||
S3JniGlobal_env_uncache( env );//SJG.envCache.aHead->env );
|
||||
}
|
||||
MUTEX_ENV_LEAVE;
|
||||
/* Do not clear S3JniGlobal.jvm: it's legal to call
|
||||
@ -3151,7 +3111,7 @@ JDECL(jint,1step)(JENV_CSELF,jobject jStmt){
|
||||
|
||||
static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
|
||||
S3JniDb * const ps = (S3JniDb *)pC;
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
jobject jX = NULL /* the tracer's X arg */;
|
||||
jobject jP = NULL /* the tracer's P arg */;
|
||||
jobject jPUnref = NULL /* potentially a local ref to jP */;
|
||||
@ -3230,7 +3190,7 @@ JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){
|
||||
static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
|
||||
const char *zTable, sqlite3_int64 nRowid){
|
||||
S3JniDb * const ps = pState;
|
||||
JNIEnv * const env = ps->env;
|
||||
LocalJniGetEnv;
|
||||
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
|
||||
jstring jDbName;
|
||||
jstring jTable;
|
||||
@ -3423,6 +3383,10 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
|
||||
"\n\tautoExt %u container access\n",
|
||||
SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2,
|
||||
SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt);
|
||||
printf("S3JniDb: %u alloced (*%u = %u bytes), %u recycled\n",
|
||||
SJG.metrics.nPdbAlloc, (unsigned) sizeof(S3JniDb),
|
||||
(unsigned)(SJG.metrics.nPdbAlloc * sizeof(S3JniDb)),
|
||||
SJG.metrics.nPdbRecycled);
|
||||
puts("Java-side UDF calls:");
|
||||
#define UDF(T) printf("\t%-8s = %u\n", "x" #T, SJG.metrics.udf.n##T)
|
||||
UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse);
|
||||
@ -3465,7 +3429,6 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
|
||||
State for binding Java-side FTS5 auxiliary functions.
|
||||
*/
|
||||
typedef struct {
|
||||
JNIEnv * env; /* env registered from */;
|
||||
jobject jObj /* functor instance */;
|
||||
jclass klazz /* jObj's class */;
|
||||
jobject jUserData /* 2nd arg to JNI binding of
|
||||
@ -3478,7 +3441,7 @@ typedef struct {
|
||||
} Fts5JniAux;
|
||||
|
||||
static void Fts5JniAux_free(Fts5JniAux * const s){
|
||||
JNIEnv * const env = s->env;
|
||||
LocalJniGetEnv;
|
||||
if(env){
|
||||
/*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
|
||||
s3jni_call_xDestroy(env, s->jObj, s->klazz);
|
||||
@ -3503,7 +3466,6 @@ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
|
||||
"Lorg/sqlite/jni/sqlite3_context;"
|
||||
"[Lorg/sqlite/jni/sqlite3_value;)V";
|
||||
memset(s, 0, sizeof(Fts5JniAux));
|
||||
s->env = env;
|
||||
s->jObj = REF_G(jObj);
|
||||
s->klazz = REF_G((*env)->GetObjectClass(env, jObj));
|
||||
s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig);
|
||||
@ -3639,14 +3601,14 @@ static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
|
||||
int argc,
|
||||
sqlite3_value **argv){
|
||||
Fts5JniAux * const pAux = pApi->xUserData(pFts);
|
||||
JNIEnv *env;
|
||||
jobject jpCx = 0;
|
||||
jobjectArray jArgv = 0;
|
||||
jobject jpFts = 0;
|
||||
jobject jFXA;
|
||||
int rc;
|
||||
LocalJniGetEnv;
|
||||
|
||||
assert(pAux);
|
||||
env = pAux->env;
|
||||
jFXA = s3jni_getFts5ExensionApi(env);
|
||||
if( !jFXA ) goto error_oom;
|
||||
jpFts = new_Fts5Context_wrapper(env, pFts);
|
||||
@ -3700,8 +3662,11 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName,
|
||||
|
||||
|
||||
typedef struct S3JniFts5AuxData S3JniFts5AuxData;
|
||||
/*
|
||||
** TODO: this middle-man struct is no longer necessary. Conider
|
||||
** removing it and passing around jObj itself instead.
|
||||
*/
|
||||
struct S3JniFts5AuxData {
|
||||
JNIEnv *env;
|
||||
jobject jObj;
|
||||
};
|
||||
|
||||
@ -3709,7 +3674,7 @@ static void S3JniFts5AuxData_xDestroy(void *x){
|
||||
if(x){
|
||||
S3JniFts5AuxData * const p = x;
|
||||
if(p->jObj){
|
||||
JNIEnv *env = p->env;
|
||||
LocalJniGetEnv;
|
||||
s3jni_call_xDestroy(env, p->jObj, 0);
|
||||
UNREF_G(p->jObj);
|
||||
}
|
||||
@ -3872,7 +3837,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
|
||||
guaranteed to be the same one passed to xQueryPhrase(). If it's
|
||||
not, we'll have to create a new wrapper object on every call. */
|
||||
struct s3jni_xQueryPhraseState const * s = pData;
|
||||
JNIEnv * const env = s->env;
|
||||
LocalJniGetEnv;
|
||||
int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
|
||||
SJG.fts5.jFtsExt, s->jFcx);
|
||||
IFTHREW{
|
||||
@ -3931,7 +3896,6 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){
|
||||
}
|
||||
return SQLITE_NOMEM;
|
||||
}
|
||||
pAux->env = env;
|
||||
pAux->jObj = REF_G(jAux);
|
||||
rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
|
||||
S3JniFts5AuxData_xDestroy);
|
||||
@ -3944,8 +3908,8 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){
|
||||
static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z,
|
||||
int nZ, int iStart, int iEnd){
|
||||
int rc;
|
||||
LocalJniGetEnv;
|
||||
struct s3jni_xQueryPhraseState * const s = p;
|
||||
JNIEnv * const env = s->env;
|
||||
jbyteArray jba;
|
||||
if( s->tok.zPrev == z && s->tok.nPrev == nZ ){
|
||||
jba = s->tok.jba;
|
||||
|
@ -18,14 +18,23 @@ package org.sqlite.jni;
|
||||
*/
|
||||
public interface AutoExtension {
|
||||
/**
|
||||
Must function as described for the sqlite3_auto_extension(),
|
||||
with the caveat that the signature is more limited.
|
||||
Must function as described for a sqlite3_auto_extension()
|
||||
callback, with the caveat that the signature is more limited.
|
||||
|
||||
As an exception (as it were) to the callbacks-must-not-throw
|
||||
rule, AutoExtensions may do so and the exception's error message
|
||||
rule, AutoExtensions may throw and the exception's error message
|
||||
will be set as the db's error string.
|
||||
|
||||
Results are undefined if db is closed by an auto-extension.
|
||||
Hints for implementations:
|
||||
|
||||
- Opening a database from an auto-extension handler will lead to
|
||||
an endless recursion of the auto-handler triggering itself
|
||||
indirectly for each newly-opened database.
|
||||
|
||||
- If this routine is stateful, it is a good idea to make the
|
||||
overridden method synchronized.
|
||||
|
||||
- Results are undefined if db is closed by an auto-extension.
|
||||
*/
|
||||
int xEntryPoint(sqlite3 db);
|
||||
}
|
||||
|
@ -173,9 +173,6 @@ public final class SQLite3Jni {
|
||||
on multiple factors).
|
||||
|
||||
See the AutoExtension class docs for more information.
|
||||
|
||||
Achtung: it is as yet unknown whether auto extensions registered
|
||||
from one JNIEnv (thread) can be safely called from another.
|
||||
*/
|
||||
public static native int sqlite3_auto_extension(@NotNull AutoExtension callback);
|
||||
|
||||
@ -212,9 +209,11 @@ public final class SQLite3Jni {
|
||||
);
|
||||
|
||||
|
||||
/** A level of indirection required to ensure that the input to the
|
||||
C-level function of the same name is a NUL-terminated UTF-8
|
||||
string. */
|
||||
/**
|
||||
A level of indirection required to ensure that the input to the
|
||||
C-level function of the same name is a NUL-terminated UTF-8
|
||||
string.
|
||||
*/
|
||||
private static native int sqlite3_bind_parameter_index(
|
||||
@NotNull sqlite3_stmt stmt, byte[] paramName
|
||||
);
|
||||
@ -226,6 +225,15 @@ public final class SQLite3Jni {
|
||||
return sqlite3_bind_parameter_index(stmt, utf8);
|
||||
}
|
||||
|
||||
/**
|
||||
Works like the C-level sqlite3_bind_text() but (A) assumes
|
||||
SQLITE_TRANSIENT for the final parameter and (B) behaves like
|
||||
sqlite3_bind_null() if the data argument is null.
|
||||
*/
|
||||
private static native int sqlite3_bind_text(
|
||||
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
|
||||
);
|
||||
|
||||
public static int sqlite3_bind_text(
|
||||
@NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
|
||||
){
|
||||
@ -242,15 +250,6 @@ public final class SQLite3Jni {
|
||||
: sqlite3_bind_text(stmt, ndx, data, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
Works like the C-level sqlite3_bind_text() but (A) assumes
|
||||
SQLITE_TRANSIENT for the final parameter and (B) behaves like
|
||||
sqlite3_bind_null() if the data argument is null.
|
||||
*/
|
||||
private static native int sqlite3_bind_text(
|
||||
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
|
||||
);
|
||||
|
||||
public static native int sqlite3_bind_zeroblob(
|
||||
@NotNull sqlite3_stmt stmt, int ndx, int n
|
||||
);
|
||||
@ -931,7 +930,8 @@ public final class SQLite3Jni {
|
||||
|
||||
If maxLength (in bytes, not characters) is larger than
|
||||
text.length, it is silently truncated to text.length. If it is
|
||||
negative, results are undefined.
|
||||
negative, results are undefined. If text is null, the following
|
||||
arguments are ignored.
|
||||
*/
|
||||
private static native void sqlite3_result_text64(
|
||||
@NotNull sqlite3_context cx, @Nullable byte[] text,
|
||||
@ -939,8 +939,9 @@ public final class SQLite3Jni {
|
||||
);
|
||||
|
||||
/**
|
||||
Cleans up all per-JNIEnv and per-db state managed by the library
|
||||
then calls the C-native sqlite3_shutdown().
|
||||
Cleans up all per-JNIEnv and per-db state managed by the library,
|
||||
as well as any registered auto-extensions, then calls the
|
||||
C-native sqlite3_shutdown().
|
||||
*/
|
||||
public static synchronized native int sqlite3_shutdown();
|
||||
|
||||
|
@ -87,9 +87,9 @@ public class Tester1 implements Runnable {
|
||||
++metrics.dbOpen;
|
||||
sqlite3 db = out.take();
|
||||
if( 0!=rc ){
|
||||
final String msg = db.getNativePointer()==0
|
||||
? sqlite3_errstr(rc)
|
||||
: sqlite3_errmsg(db);
|
||||
final String msg =
|
||||
null==db ? sqlite3_errstr(rc) : sqlite3_errmsg(db);
|
||||
sqlite3_close(db);
|
||||
throw new RuntimeException("Opening db failed: "+msg);
|
||||
}
|
||||
affirm( null == out.get() );
|
||||
@ -428,6 +428,7 @@ public class Tester1 implements Runnable {
|
||||
sqlite3_bind_text(stmt, 1, "hell😃");
|
||||
affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) );
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_close(db);
|
||||
}
|
||||
|
||||
private void testCollation(){
|
||||
@ -500,14 +501,12 @@ public class Tester1 implements Runnable {
|
||||
rc = sqlite3_collation_needed(db, null);
|
||||
affirm( 0 == rc );
|
||||
sqlite3_close_v2(db);
|
||||
affirm( 0 == db.getNativePointer() );
|
||||
affirm(xDestroyCalled.value);
|
||||
}
|
||||
|
||||
private void testToUtf8(){
|
||||
/**
|
||||
Java docs seem contradictory, claiming to use "modified UTF-8"
|
||||
encoding while also claiming to export using RFC 2279:
|
||||
|
||||
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
|
||||
|
||||
Let's ensure that we can convert to standard UTF-8 in Java code
|
||||
@ -1106,6 +1105,7 @@ public class Tester1 implements Runnable {
|
||||
execSql(db, "ATTACH ':memory' as foo");
|
||||
affirm( 4==val.value /* ATTACH uses the same connection, not sub-connections. */ );
|
||||
sqlite3_close(db);
|
||||
db = null;
|
||||
|
||||
affirm( sqlite3_cancel_auto_extension(ax) );
|
||||
affirm( !sqlite3_cancel_auto_extension(ax) );
|
||||
@ -1116,7 +1116,7 @@ public class Tester1 implements Runnable {
|
||||
Exception err = null;
|
||||
toss.value = "Throwing from AutoExtension.";
|
||||
try{
|
||||
createNewDb();
|
||||
sqlite3_close(createNewDb());
|
||||
}catch(Exception e){
|
||||
err = e;
|
||||
}
|
||||
@ -1169,9 +1169,11 @@ public class Tester1 implements Runnable {
|
||||
|
||||
private void runTests(boolean fromThread) throws Exception {
|
||||
if(false) testCompileOption();
|
||||
testToUtf8();
|
||||
test1();
|
||||
testOpenDb1();
|
||||
testOpenDb2();
|
||||
testCollation();
|
||||
testPrepare123();
|
||||
testBindFetchInt();
|
||||
testBindFetchInt64();
|
||||
@ -1179,8 +1181,6 @@ public class Tester1 implements Runnable {
|
||||
testBindFetchText();
|
||||
testBindFetchBlob();
|
||||
testSql();
|
||||
testCollation();
|
||||
testToUtf8();
|
||||
testStatus();
|
||||
testUdf1();
|
||||
testUdfJavaObject();
|
||||
|
Reference in New Issue
Block a user