blob: 7c4aa7c28befd927f0c8b51337a3763be8249ffd [file] [log] [blame] [edit]
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file implements the JNI bindings declared in
** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated).
*/
/*
** If you found this comment by searching the code for
** CallStaticObjectMethod then you're the victim of an OpenJDK bug:
**
** https://bugs.openjdk.org/browse/JDK-8130659
**
** It's known to happen with OpenJDK v8 but not with v19.
**
** This code does not use JNI's CallStaticObjectMethod().
*/
/*
** Define any SQLITE_... config defaults we want if they aren't
** overridden by the builder. Please keep these alphabetized.
*/
/**********************************************************************/
/* SQLITE_D... */
#ifndef SQLITE_DEFAULT_CACHE_SIZE
# define SQLITE_DEFAULT_CACHE_SIZE -16384
#endif
#if !defined(SQLITE_DEFAULT_PAGE_SIZE)
# define SQLITE_DEFAULT_PAGE_SIZE 8192
#endif
#ifndef SQLITE_DQS
# define SQLITE_DQS 0
#endif
/**********************************************************************/
/* SQLITE_ENABLE_... */
#ifndef SQLITE_ENABLE_BYTECODE_VTAB
# define SQLITE_ENABLE_BYTECODE_VTAB 1
#endif
#ifndef SQLITE_ENABLE_DBPAGE_VTAB
# define SQLITE_ENABLE_DBPAGE_VTAB 1
#endif
#ifndef SQLITE_ENABLE_DBSTAT_VTAB
# define SQLITE_ENABLE_DBSTAT_VTAB 1
#endif
#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS
# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1
#endif
#ifndef SQLITE_ENABLE_MATH_FUNCTIONS
# define SQLITE_ENABLE_MATH_FUNCTIONS 1
#endif
#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC
# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1
#endif
#ifndef SQLITE_ENABLE_RTREE
# define SQLITE_ENABLE_RTREE 1
#endif
//#ifndef SQLITE_ENABLE_SESSION
//# define SQLITE_ENABLE_SESSION 1
//#endif
#ifndef SQLITE_ENABLE_STMTVTAB
# define SQLITE_ENABLE_STMTVTAB 1
#endif
//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
//#endif
/**********************************************************************/
/* SQLITE_M... */
#ifndef SQLITE_MAX_ALLOCATION_SIZE
# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff
#endif
/**********************************************************************/
/* SQLITE_O... */
#ifndef SQLITE_OMIT_DEPRECATED
# define SQLITE_OMIT_DEPRECATED 1
#endif
#ifndef SQLITE_OMIT_LOAD_EXTENSION
# define SQLITE_OMIT_LOAD_EXTENSION 1
#endif
#ifndef SQLITE_OMIT_SHARED_CACHE
# define SQLITE_OMIT_SHARED_CACHE 1
#endif
#ifdef SQLITE_OMIT_UTF16
/* UTF16 is required for java */
# undef SQLITE_OMIT_UTF16 1
#endif
/**********************************************************************/
/* SQLITE_T... */
#ifndef SQLITE_TEMP_STORE
# define SQLITE_TEMP_STORE 2
#endif
#ifndef SQLITE_THREADSAFE
# define SQLITE_THREADSAFE 1
#endif
/**********************************************************************/
/* SQLITE_USE_... */
#ifndef SQLITE_USE_URI
# define SQLITE_USE_URI 1
#endif
/*
** Which sqlite3.c we're using needs to be configurable to enable
** building against a custom copy, e.g. the SEE variant. We have to
** include sqlite3.c, as opposed to sqlite3.h, in order to get access
** to SQLITE_MAX_... and friends. This increases the rebuild time
** considerably but we need this in order to keep the exported values
** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build.
*/
#ifndef SQLITE_C
# define SQLITE_C sqlite3.c
#endif
#define INC__STRINGIFY_(f) #f
#define INC__STRINGIFY(f) INC__STRINGIFY_(f)
#include INC__STRINGIFY(SQLITE_C)
#undef INC__STRINGIFY_
#undef INC__STRINGIFY
#undef SQLITE_C
/*
** End of the sqlite3 lib setup. What follows is JNI-specific.
*/
#include "sqlite3-jni.h"
#include <assert.h>
#include <stdio.h> /* only for testing/debugging */
/* Only for debugging */
#define MARKER(pfexp) \
do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
printf pfexp; \
} while(0)
/* Creates a verbose JNI function name. */
#define JFuncName(Suffix) \
Java_org_sqlite_jni_SQLite3Jni_sqlite3_ ## Suffix
/* Prologue for JNI functions. */
#define JDECL(ReturnType,Suffix) \
JNIEXPORT ReturnType JNICALL \
JFuncName(Suffix)
/*
** Shortcuts for the first 2 parameters to all JNI bindings.
**
** The type of the jSelf arg differs, but no docs seem to mention
** this: for static methods it's of type jclass and for non-static
** it's jobject. jobject actually works for all funcs, in the sense
** that it compiles and runs so long as we don't use jSelf (which is
** only rarely needed in this code), but to be pedantically correct we
** need the proper type in the signature.
**
** https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers
*/
#define JENV_OSELF JNIEnv * const env, jobject jSelf
#define JENV_CSELF JNIEnv * const env, jclass jKlazz
/*
** Helpers to account for -Xcheck:jni warnings about not having
** checked for exceptions.
*/
#define IFTHREW if((*env)->ExceptionCheck(env))
#define EXCEPTION_CLEAR (*env)->ExceptionClear(env)
#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env)
#define EXCEPTION_IGNORE IFTHREW EXCEPTION_CLEAR
#define EXCEPTION_WARN_IGNORE \
IFTHREW {EXCEPTION_REPORT; EXCEPTION_CLEAR;}(void)0
#define EXCEPTION_WARN_CALLBACK_THREW(STR) \
MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \
(*env)->ExceptionDescribe(env)
#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT
#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR
/** To be used for cases where we're _really_ not expecting an
exception, e.g. looking up well-defined Java class members. */
#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\
EXCEPTION_REPORT; EXCEPTION_CLEAR; \
(*env)->FatalError(env, MSG); \
}
/** Helpers for extracting pointers from jobjects, noting that the
corresponding Java interfaces have already done the type-checking.
*/
#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3)
#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_stmt)
#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_value)
#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.sqlite3_context)
/* Helpers for Java value reference management. */
static inline jobject new_global_ref(JNIEnv * const env, jobject const v){
return v ? (*env)->NewGlobalRef(env, v) : NULL;
}
static inline void delete_global_ref(JNIEnv * const env, jobject const v){
if(v) (*env)->DeleteGlobalRef(env, v);
}
static inline void delete_local_ref(JNIEnv * const env, jobject const v){
if(v) (*env)->DeleteLocalRef(env, v);
}
#define REF_G(VAR) new_global_ref(env, (VAR))
#define REF_L(VAR) (*env)->NewLocalRef(env, VAR)
#define UNREF_G(VAR) delete_global_ref(env,(VAR))
#define UNREF_L(VAR) delete_local_ref(env,(VAR))
/**
Keys for use with S3JniGlobal_nph_cache().
*/
typedef struct S3NphRef S3NphRef;
struct S3NphRef {
const int index /* index into S3JniGlobal.nph[] */;
const char * const zName /* Full Java name of the class */;
};
/**
Keys for each concrete NativePointerHolder subclass. These are to
be used with S3JniGlobal_nph_cache() and friends. These are
initialized on-demand by S3JniGlobal_nph_cache().
*/
static const struct {
const S3NphRef sqlite3;
const S3NphRef sqlite3_stmt;
const S3NphRef sqlite3_context;
const S3NphRef sqlite3_value;
const S3NphRef OutputPointer_Int32;
const S3NphRef OutputPointer_Int64;
const S3NphRef OutputPointer_sqlite3;
const S3NphRef OutputPointer_sqlite3_stmt;
const S3NphRef OutputPointer_sqlite3_value;
#ifdef SQLITE_ENABLE_FTS5
const S3NphRef OutputPointer_String;
const S3NphRef OutputPointer_ByteArray;
const S3NphRef Fts5Context;
const S3NphRef Fts5ExtensionApi;
const S3NphRef fts5_api;
const S3NphRef fts5_tokenizer;
const S3NphRef Fts5Tokenizer;
#endif
} S3NphRefs = {
#define NREF(INDEX, JAVANAME) { INDEX, "org/sqlite/jni/" JAVANAME }
NREF(0, "sqlite3"),
NREF(1, "sqlite3_stmt"),
NREF(2, "sqlite3_context"),
NREF(3, "sqlite3_value"),
NREF(4, "OutputPointer$Int32"),
NREF(5, "OutputPointer$Int64"),
NREF(6, "OutputPointer$sqlite3"),
NREF(7, "OutputPointer$sqlite3_stmt"),
NREF(8, "OutputPointer$sqlite3_value"),
#ifdef SQLITE_ENABLE_FTS5
NREF(9, "OutputPointer$String"),
NREF(10, "OutputPointer$ByteArray"),
NREF(11, "Fts5Context"),
NREF(12, "Fts5ExtensionApi"),
NREF(13, "fts5_api"),
NREF(14, "fts5_tokenizer"),
NREF(15, "Fts5Tokenizer")
#endif
#undef NREF
};
/** Create a trivial JNI wrapper for (int CName(void)). */
#define WRAP_INT_VOID(JniNameSuffix,CName) \
JDECL(jint,JniNameSuffix)(JENV_CSELF){ \
return (jint)CName(); \
}
/** Create a trivial JNI wrapper for (int CName(int)). */
#define WRAP_INT_INT(JniNameSuffix,CName) \
JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){ \
return (jint)CName((int)arg); \
}
/*
** Create a trivial JNI wrapper for (const mutf8_string *
** CName(void)). This is only valid for functions which are known to
** return ASCII or text which is equivalent in UTF-8 and MUTF-8.
*/
#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \
JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \
return (*env)->NewStringUTF( env, CName() ); \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */
#define WRAP_INT_STMT(JniNameSuffix,CName) \
JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){ \
jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \
EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \
return rc; \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */
#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \
JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \
return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \
}
/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */
#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \
JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \
return s3jni_utf8_to_jstring(env, \
CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx), \
-1); \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */
#define WRAP_INT_DB(JniNameSuffix,CName) \
JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){ \
return (jint)CName(PtrGet_sqlite3(pDb)); \
}
/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */
#define WRAP_INT64_DB(JniNameSuffix,CName) \
JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){ \
return (jlong)CName(PtrGet_sqlite3(pDb)); \
}
/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */
#define WRAP_INT_SVALUE(JniNameSuffix,CName) \
JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){ \
return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \
}
/* Helpers for jstring and jbyteArray. */
#define s3jni_jstring_to_mutf8(ARG) (*env)->GetStringUTFChars(env, ARG, NULL)
#define s3jni_mutf8_release(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR)
#define s3jni_jbytearray_bytes(ARG) (*env)->GetByteArrayElements(env,ARG, NULL)
#define s3jni_jbytearray_release(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT)
enum {
/*
** Size of the NativePointerHolder cache. Need enough space for
** (only) the library's NativePointerHolder types, a fixed count
** known at build-time. If we add more than this a fatal error will
** be triggered with a reminder to increase this. This value needs
** to be exactly the number of entries in the S3NphRefs object. The
** index field of those entries are the keys for this particular
** cache.
*/
NphCache_SIZE = sizeof(S3NphRefs) / sizeof(S3NphRef)
};
/*
** Cache entry for NativePointerHolder subclasses and OutputPointer
** types.
*/
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 */;
volatile jmethodID midCtor /* klazz's no-arg constructor. Used by
** new_NativePointerHolder_object(). */;
volatile jfieldID fidValue /* NativePointerHolder.nativePointer or
** OutputPointer.T.value */;
volatile jfieldID fidAggCtx /* sqlite3_context::aggregateContext. Used only
** by the sqlite3_context binding. */;
};
/** State for various hook callbacks. */
typedef struct S3JniHook S3JniHook;
struct S3JniHook{
jobject jObj /* global ref to Java instance */;
jmethodID midCallback /* callback method. Signature depends on
** jObj's type */;
};
/*
** Per-(sqlite3*) state for various JNI bindings. This state is
** allocated as needed, cleaned up in sqlite3_close(_v2)(), and
** recycled when possible.
*/
typedef struct S3JniDb S3JniDb;
struct S3JniDb {
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
order to have an object to pass to routines like
sqlite3_collation_needed()'s callback, or else we
have to dynamically create one for that purpose,
which would be fine except that it would be a
different instance (and maybe even a different
class) than the one the user may expect to
receive. */;
char * zMainDbName /* Holds the string allocated on behalf of
SQLITE_DBCONFIG_MAINDBNAME. */;
struct {
S3JniHook busyHandler;
S3JniHook collation;
S3JniHook collationNeeded;
S3JniHook commit;
S3JniHook progress;
S3JniHook rollback;
S3JniHook trace;
S3JniHook update;
S3JniHook auth;
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
S3JniHook preUpdate;
#endif
} hooks;
#ifdef SQLITE_ENABLE_FTS5
jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */;
#endif
S3JniDb * pNext /* Next entry in the available/free list */;
S3JniDb * pPrev /* Previous entry in the available/free list */;
};
/*
** Cache for per-JNIEnv (i.e. per-thread) data.
*/
typedef struct S3JniEnv S3JniEnv;
struct S3JniEnv {
JNIEnv *env /* env in which this cache entry was created */;
/*
** pdbOpening is used to coordinate the Java/DB connection of a
** being-open()'d db in the face of auto-extensions. "The problem"
** is that auto-extensions run before we can bind the C db to its
** Java representation, but auto-extensions require that binding. We
** handle this as follows:
**
** - In the JNI side of sqlite3_open(), allocate the Java side of
** that connection and set pdbOpening to point to that
** object.
**
** - Call sqlite3_open(), which triggers the auto-extension
** handler. That handler uses pdbOpening to connect the native
** db handle which it receives with pdbOpening.
**
** - When sqlite3_open() returns, check whether pdbOpening->pDb is
** NULL. If it isn't, auto-extension handling set it up. If it
** is, complete the Java/C binding unless sqlite3_open() returns
** a NULL db, in which case free pdbOpening.
*/
S3JniDb * pdbOpening;
S3JniEnv * pPrev /* Previous entry in the linked list */;
S3JniEnv * pNext /* Next entry in the linked list */;
};
/*
** State for proxying sqlite3_auto_extension() in Java.
*/
typedef struct S3JniAutoExtension S3JniAutoExtension;
struct S3JniAutoExtension {
jobject jObj /* Java object */;
jmethodID midFunc /* xEntryPoint() callback */;
};
/*
** If true, modifying S3JniGlobal.metrics is protected by a mutex,
** else it isn't.
*/
#ifdef SQLITE_DEBUG
#define S3JNI_METRICS_MUTEX 1
#else
#define S3JNI_METRICS_MUTEX 0
#endif
/*
** Global state, e.g. caches and metrics.
*/
typedef struct S3JniGlobalType S3JniGlobalType;
struct S3JniGlobalType {
/*
** According to: https://developer.ibm.com/articles/j-jni/
**
** > A thread can get a JNIEnv by calling GetEnv() using the JNI
** invocation interface through a JavaVM object. The JavaVM object
** itself can be obtained by calling the JNI GetJavaVM() method
** using a JNIEnv object and can be cached and shared across
** threads. Caching a copy of the JavaVM object enables any thread
** with access to the cached object to get access to its own
** JNIEnv when necessary.
*/
JavaVM * jvm;
/* Cache of Java refs/IDs for NativePointerHolder subclasses.
** Initialized on demand.
*/
S3JniNphClass nph[NphCache_SIZE];
/*
** Cache of per-thread state.
*/
struct {
S3JniEnv * aHead /* Linked list of in-use instances */;
S3JniEnv * aFree /* Linked list of free instances */;
sqlite3_mutex * mutex /* mutex for aHead and aFree as well for
first-time inits of nph members. */;
void const * locker /* env mutex is held on this object's behalf.
Used only for sanity checking. */;
} envCache;
struct {
S3JniDb * aUsed /* Linked list of in-use instances */;
S3JniDb * aFree /* Linked list of free instances */;
sqlite3_mutex * mutex /* mutex for aUsed and aFree */;
void const * locker /* perDb mutex is held on this object's
behalf. Unlike envCache.locker, we cannot
always have this set to the current JNIEnv
object. Used only for sanity checking. */;
} perDb;
#ifdef SQLITE_ENABLE_SQLLOG
struct {
S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
} hooks;
#endif
/*
** Refs to global classes and methods. Obtained during static init
** and never released.
*/
struct {
jclass cObj /* global ref to java.lang.Object */;
jclass cLong /* global ref to java.lang.Long */;
jclass cString /* global ref to java.lang.String */;
jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */;
jmethodID ctorLong1 /* the Long(long) constructor */;
jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
} g /* refs to global Java state */;
#ifdef SQLITE_ENABLE_FTS5
struct {
volatile jobject jFtsExt /* Global ref to Java singleton for the
Fts5ExtensionApi instance. */;
struct {
jfieldID fidA /* Fts5Phrase::a member */;
jfieldID fidB /* Fts5Phrase::b member */;
} jPhraseIter;
} fts5;
#endif
/* Internal metrics. */
struct {
volatile unsigned envCacheHits;
volatile unsigned envCacheMisses;
volatile unsigned envCacheAllocs;
volatile unsigned nMutexEnv /* number of times envCache.mutex was entered for
a S3JniEnv operation. */;
volatile unsigned nMutexEnv2 /* number of times envCache.mutex was entered for
a S3JniNphClass operation. */;
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;
volatile unsigned nStep;
volatile unsigned nFinal;
volatile unsigned nValue;
volatile unsigned nInverse;
} udf;
unsigned nMetrics /* Total number of mutex-locked metrics increments. */;
#if S3JNI_METRICS_MUTEX
sqlite3_mutex * mutex;
#endif
} metrics;
/**
The list of bound auto-extensions (Java-side:
org.sqlite.jni.AutoExtension objects).
*/
struct {
S3JniAutoExtension *pExt /* Head of the auto-extension list */;
int nAlloc /* number of entries allocated for pExt,
as distinct from the number of active
entries. */;
int nExt /* number of active entries in pExt. */;
sqlite3_mutex * mutex /* mutex for manipulation/traversal of pExt */;
} autoExt;
};
static S3JniGlobalType S3JniGlobal = {};
#define SJG S3JniGlobal
/* Increments *p, possibly protected by a mutex. */
#if S3JNI_METRICS_MUTEX
static void s3jni_incr( volatile unsigned int * const p ){
sqlite3_mutex * const m = SJG.metrics.mutex;
sqlite3_mutex_enter(m);
++SJG.metrics.nMetrics;
++(*p);
sqlite3_mutex_leave(m);
}
#else
#define s3jni_incr(PTR) ++(*(PTR))
#endif
/* Helpers for working with specific mutexes. */
#define MUTEX_ENV_ASSERT_LOCKED \
assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ENV_ASSERT_LOCKER \
assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ENV_ASSERT_NOTLOCKER \
assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define MUTEX_ENV_ENTER \
MUTEX_ENV_ASSERT_NOTLOCKER; \
/*MARKER(("Entering ENV mutex@%p %s.\n", env));*/ \
sqlite3_mutex_enter( SJG.envCache.mutex ); \
++SJG.metrics.nMutexEnv; \
SJG.envCache.locker = env
#define MUTEX_ENV_LEAVE \
/*MARKER(("Leaving ENV mutex @%p %s.\n", env));*/ \
MUTEX_ENV_ASSERT_LOCKER; \
SJG.envCache.locker = 0; \
sqlite3_mutex_leave( SJG.envCache.mutex )
#define MUTEX_EXT_ENTER \
/*MARKER(("Entering autoExt mutex@%p %s.\n", env));*/ \
sqlite3_mutex_enter( SJG.autoExt.mutex ); \
++SJG.metrics.nMutexAutoExt
#define MUTEX_EXT_LEAVE \
/*MARKER(("Leaving autoExt mutex@%p %s.\n", env));*/ \
sqlite3_mutex_leave( SJG.autoExt.mutex )
#define MUTEX_NPH_ENTER \
MUTEX_ENV_ASSERT_NOTLOCKER; \
/*MARKER(("Entering NPH mutex@%p %s.\n", env));*/ \
sqlite3_mutex_enter( SJG.envCache.mutex ); \
++SJG.metrics.nMutexEnv2; \
SJG.envCache.locker = env
#define MUTEX_NPH_LEAVE \
/*MARKER(("Leaving NPH mutex @%p %s.\n", env));*/ \
MUTEX_ENV_ASSERT_LOCKER; \
SJG.envCache.locker = 0; \
sqlite3_mutex_leave( SJG.envCache.mutex )
#define MUTEX_PDB_ENTER \
/*MARKER(("Entering PerDb mutex@%p %s.\n", env));*/ \
sqlite3_mutex_enter( SJG.perDb.mutex ); \
++SJG.metrics.nMutexPerDb; \
SJG.perDb.locker = env;
#define MUTEX_PDB_LEAVE \
/*MARKER(("Leaving PerDb mutex@%p %s.\n", env));*/ \
SJG.perDb.locker = 0; \
sqlite3_mutex_leave( SJG.perDb.mutex )
#define s3jni_oom_check(VAR) if(!(VAR)) s3jni_oom(env)
static inline void s3jni_oom(JNIEnv * const env){
(*env)->FatalError(env, "Out of memory.") /* does not return */;
}
/**
sqlite3_malloc() proxy which fails fatally on OOM. This should
only be used for routines which manage global state and have no
recovery strategy for OOM. For sqlite3 API which can reasonably
return SQLITE_NOMEM, sqlite3_malloc() should be used instead.
*/
static void * s3jni_malloc(JNIEnv * const env, size_t n){
void * const rv = sqlite3_malloc(n);
if(n && !rv) s3jni_oom(env);
return rv;
}
/**
Fetches the S3JniGlobal.envCache row for the given env, allocing
a row if needed. When a row is allocated, its state is initialized
insofar as possible. Calls (*env)->FatalError() if allocation of
an entry fails. That's hypothetically possible but "shouldn't happen."
*/
static S3JniEnv * S3JniGlobal_env_cache(JNIEnv * const env){
struct S3JniEnv * row;
MUTEX_ENV_ENTER;
row = SJG.envCache.aHead;
for( ; row; row = row->pNext ){
if( row->env == env ){
s3jni_incr( &SJG.metrics.envCacheHits );
MUTEX_ENV_LEAVE;
return row;
}
}
s3jni_incr( &SJG.metrics.envCacheMisses );
row = SJG.envCache.aFree;
if( row ){
assert(!row->pPrev);
SJG.envCache.aFree = row->pNext;
if( row->pNext ) row->pNext->pPrev = 0;
}else{
row = s3jni_malloc(env, sizeof(S3JniEnv));
s3jni_incr( &SJG.metrics.envCacheAllocs );
}
memset(row, 0, sizeof(*row));
row->pNext = SJG.envCache.aHead;
if(row->pNext) row->pNext->pPrev = row;
SJG.envCache.aHead = row;
row->env = env;
MUTEX_ENV_LEAVE;
return row;
}
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own Java/JNI bindings.
**
** For purposes of certain hand-crafted JNI function bindings, we
** need a way of reporting errors which is consistent with the rest of
** the C API, as opposed to throwing JS exceptions. To that end, this
** internal-use-only function is a thin proxy around
** sqlite3ErrorWithMessage(). The intent is that it only be used from
** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
** from client code.
**
** Returns err_code.
*/
static int s3jni_db_error(sqlite3* const db, int err_code,
const char * const zMsg){
if( db!=0 ){
if( 0==zMsg ){
sqlite3Error(db, err_code);
}else{
const int nMsg = sqlite3Strlen30(zMsg);
sqlite3_mutex_enter(sqlite3_db_mutex(db));
sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
sqlite3_mutex_leave(sqlite3_db_mutex(db));
}
}
return err_code;
}
/*
** Creates a new jByteArray of length nP, copies p's contents into it, and
** returns that byte array (NULL on OOM).
*/
static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env,
const unsigned char * const p, int nP){
jbyteArray jba = (*env)->NewByteArray(env, (jint)nP);
if(jba){
(*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p);
}
return jba;
}
/*
** Returns the current JNIEnv object. Fails fatally if it cannot find
** it.
*/
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;
}
/* Declares local var env = s3jni_get_env(). */
#define LocalJniGetEnv JNIEnv * const env = s3jni_get_env()
/*
** Uses the java.lang.String(byte[],Charset) constructor to create a
** new String from UTF-8 string z. n is the number of bytes to
** copy. If n<0 then sqlite3Strlen30() is used to calculate it.
**
** Returns NULL if z is NULL or on OOM, else returns a new jstring
** owned by the caller.
**
** Sidebar: this is a painfully inefficient way to convert from
** standard UTF-8 to a Java string, but JNI offers only algorithms for
** working with MUTF-8, not UTF-8.
*/
static jstring s3jni_utf8_to_jstring(JNIEnv * const env,
const char * const z, int n){
jstring rv = NULL;
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
NUL-terminated and none of z[0..3] are NUL bytes. */
rv = (*env)->NewStringUTF(env, "");
}else if( z ){
jbyteArray jba;
if( n<0 ) n = sqlite3Strlen30(z);
jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n);
if( jba ){
rv = (*env)->NewObject(env, SJG.g.cString, SJG.g.ctorStringBA,
jba, SJG.g.oCharsetUtf8);
UNREF_L(jba);
}
}
return rv;
}
/*
** Converts the given java.lang.String object into a NUL-terminated
** UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8).
** Returns NULL if jstr is NULL or on allocation error. If jstr is not
** NULL and nLen is not NULL then nLen is set to the length of the
** returned string, not including the terminating NUL. If jstr is not
** NULL and it returns NULL, this indicates an allocation error. In
** that case, if nLen is not NULL then it is either set to 0 (if
** fetching of jstr's bytes fails to allocate) or set to what would
** have been the length of the string had C-string allocation
** succeeded.
**
** The returned memory is allocated from sqlite3_malloc() and
** ownership is transferred to the caller.
*/
static char * s3jni_jstring_to_utf8(JNIEnv * const env,
jstring jstr, int *nLen){
jbyteArray jba;
jsize nBa;
char *rv;
if(!jstr) return 0;
jba = (*env)->CallObjectMethod(env, jstr, SJG.g.stringGetBytes,
SJG.g.oCharsetUtf8);
if( (*env)->ExceptionCheck(env) || !jba
/* order of these checks is significant for -Xlint:jni */ ) {
EXCEPTION_REPORT;
if( nLen ) *nLen = 0;
return 0;
}
nBa = (*env)->GetArrayLength(env, jba);
if( nLen ) *nLen = (int)nBa;
rv = sqlite3_malloc( nBa + 1 );
if( rv ){
(*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv);
rv[nBa] = 0;
}
UNREF_L(jba);
return rv;
}
/*
** Expects to be passed a pointer from sqlite3_column_text16() or
** sqlite3_value_text16() and a byte-length value from
** sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a
** Java String of exactly half that character length, returning NULL
** if !p or (*env)->NewString() fails.
*/
static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){
return p
? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2))
: NULL;
}
/*
** Requires jx to be a Throwable. Calls its toString() 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.
**
** Design note: we use toString() instead of getMessage() because the
** former includes the exception type's name:
**
** Exception e = new RuntimeException("Hi");
** System.out.println(e.toString()); // java.lang.RuntimeException: Hi
** System.out.println(e.getMessage()); // Hi
*/
static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){
jmethodID mid;
jstring msg;
char * zMsg;
jclass const klazz = (*env)->GetObjectClass(env, jx);
mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;");
UNREF_L(klazz);
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(env, msg, 0);
UNREF_L(msg);
return zMsg;
}
/*
** Extracts the current JNI exception, sets ps->pDb's error message to
** its message string, and clears the exception. If errCode is non-0,
** it is used as-is, else SQLITE_ERROR is assumed. If there's a
** problem extracting the exception's message, it's treated as
** non-fatal and zDfltMsg is used in its place.
**
** This must only be called if a JNI exception is pending.
**
** Returns errCode unless it is 0, in which case SQLITE_ERROR is
** returned.
*/
static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps,
int errCode, const char *zDfltMsg){
jthrowable const ex = (*env)->ExceptionOccurred(env);
if( 0==errCode ) errCode = SQLITE_ERROR;
if( ex ){
char * zMsg;
EXCEPTION_CLEAR;
zMsg = s3jni_exception_error_msg(env, ex);
s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg);
sqlite3_free(zMsg);
UNREF_L(ex);
}
return errCode;
}
/*
** Extracts the (void xDestroy()) method from jObj and applies it to
** jObj. If jObj is NULL, this is a no-op. The lack of an xDestroy()
** method is silently ignored and any exceptions thrown by xDestroy()
** trigger a warning to stdout or stderr and then the exception is
** suppressed.
*/
static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj){
if(jObj){
jclass const klazz = (*env)->GetObjectClass(env, jObj);
jmethodID method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V");
UNREF_L(klazz);
if(method){
s3jni_incr( &SJG.metrics.nDestroy );
(*env)->CallVoidMethod(env, jObj, method);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback");
EXCEPTION_CLEAR;
}
}else{
/* Non-fatal. */
EXCEPTION_CLEAR;
}
}
}
/*
** Removes any Java references from s and clears its state. If
** doXDestroy is true and s->jObj is not NULL, s->jObj's
** s is passed to s3jni_call_xDestroy() before any references are
** cleared. It is legal to call this when the object has no Java
** references.
*/
static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){
if(doXDestroy && s->jObj){
s3jni_call_xDestroy(env, s->jObj);
}
UNREF_G(s->jObj);
memset(s, 0, sizeof(*s));
}
/*
** Clears s's state and moves it to the free-list.
*/
static void S3JniDb_set_aside(JNIEnv * env, S3JniDb * const s){
if(s){
MUTEX_PDB_ENTER;
assert(s->pPrev != s);
assert(s->pNext != s);
assert(s->pPrev ? (s->pPrev!=s->pNext) : 1);
if(s->pNext) s->pNext->pPrev = s->pPrev;
if(s->pPrev) s->pPrev->pNext = s->pNext;
else if(SJG.perDb.aUsed == s){
assert(!s->pPrev);
SJG.perDb.aUsed = s->pNext;
}
sqlite3_free( s->zMainDbName );
#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->hooks.MEMBER, XDESTROY)
UNHOOK(trace, 0);
UNHOOK(progress, 0);
UNHOOK(commit, 0);
UNHOOK(rollback, 0);
UNHOOK(update, 0);
UNHOOK(auth, 0);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
UNHOOK(preUpdate, 0);
#endif
UNHOOK(collation, 1);
UNHOOK(collationNeeded, 1);
UNHOOK(busyHandler, 1);
#undef UNHOOK
UNREF_G(s->jDb);
memset(s, 0, sizeof(S3JniDb));
s->pNext = SJG.perDb.aFree;
if(s->pNext) s->pNext->pPrev = s;
SJG.perDb.aFree = s;
MUTEX_PDB_LEAVE;
}
}
/*
** Uncache any state for the given JNIEnv, clearing all Java
** references the cache owns. Returns true if env was cached and false
** if it was not found in the cache.
*/
static int S3JniGlobal_env_uncache(JNIEnv * const env){
struct S3JniEnv * row;
MUTEX_ENV_ASSERT_LOCKED;
row = SJG.envCache.aHead;
for( ; row; row = row->pNext ){
if( row->env == env ){
break;
}
}
if( !row ){
return 0;
}
if( row->pNext ) row->pNext->pPrev = row->pPrev;
if( row->pPrev ) row->pPrev->pNext = row->pNext;
if( SJG.envCache.aHead == row ){
assert( !row->pPrev );
SJG.envCache.aHead = row->pNext;
}
memset(row, 0, sizeof(S3JniEnv));
row->pNext = SJG.envCache.aFree;
if( row->pNext ) row->pNext->pPrev = row;
SJG.envCache.aFree = row;
return 1;
}
/*
** Searches the NativePointerHolder cache for the given combination of
** args. It returns a cache entry with its klazz member set.
**
** It is up to the caller to populate the other members of the
** returned object if needed, taking care to lock the population with
** MUTEX_NPH_ENTER/LEAVE.
**
** This simple cache catches >99% of searches in the current
** (2023-07-31) tests.
*/
static S3JniNphClass * S3JniGlobal_nph_cache(JNIEnv * const env, S3NphRef const* pRef){
/**
According to:
https://developer.ibm.com/articles/j-jni/
> ... the IDs returned for a given class don't change for the
lifetime of the JVM process. But the call to get the field or
method can require significant work in the JVM, because
fields and methods might have been inherited from
superclasses, making the JVM walk up the class hierarchy to
find them. Because the IDs are the same for a given class,
you should look them up once and then reuse them. Similarly,
looking up class objects can be expensive, so they should be
cached as well.
*/
S3JniNphClass * const pNC = &SJG.nph[pRef->index];
if( !pNC->pRef ){
MUTEX_NPH_ENTER;
if( !pNC->pRef ){
pNC->pRef = pRef;
pNC->klazz = (*env)->FindClass(env, pRef->zName);
EXCEPTION_IS_FATAL("FindClass() unexpectedly threw");
pNC->klazz = REF_G(pNC->klazz);
}
MUTEX_NPH_LEAVE;
}
return pNC;
}
/*
** Returns the ID of the "nativePointer" field from the given
** NativePointerHolder<T> class.
*/
static jfieldID NativePointerHolder_getField(JNIEnv * const env, S3NphRef const* pRef){
S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef);
if(!pNC->fidValue){
MUTEX_NPH_ENTER;
if(!pNC->fidValue){
pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, "nativePointer", "J");
EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field.");
}
MUTEX_NPH_LEAVE;
}
return pNC->fidValue;
}
/*
** Sets a native ptr value in NativePointerHolder object ppOut.
** zClassName must be a static string so we can use its address
** as a cache key.
*/
static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p,
S3NphRef const* pRef){
(*env)->SetLongField(env, ppOut, NativePointerHolder_getField(env, pRef),
(jlong)p);
EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer.");
}
/*
** Fetches a native ptr value from NativePointerHolder object ppOut.
** zClassName must be a static string so we can use its address as a
** cache key. This is a no-op if pObj is NULL.
*/
static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, S3NphRef const* pRef){
if( pObj ){
void * rv = (void*)(*env)->GetLongField(
env, pObj, NativePointerHolder_getField(env, pRef)
);
EXCEPTION_IS_FATAL("Cannot fetch NativePointerHolder.nativePointer.");
return rv;
}else{
return 0;
}
}
/*
** Extracts the new S3JniDb instance from the free-list, or allocates
** one if needed, associats it with pDb, and returns. Returns NULL on
** OOM. pDb MUST, on success of the calling operation, subsequently be
** associated with jDb via NativePointerHolder_set().
*/
static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
jobject jDb){
S3JniDb * rv;
MUTEX_PDB_ENTER;
if( SJG.perDb.aFree ){
rv = SJG.perDb.aFree;
SJG.perDb.aFree = rv->pNext;
assert(rv->pNext != rv);
assert(rv->pPrev != rv);
assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1);
if(rv->pNext){
assert(rv->pNext->pPrev == rv);
assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1);
rv->pNext->pPrev = 0;
rv->pNext = 0;
}
s3jni_incr( &SJG.metrics.nPdbRecycled );
}else{
rv = s3jni_malloc(env, sizeof(S3JniDb));
if( rv ){
memset(rv, 0, sizeof(S3JniDb));
s3jni_incr( &SJG.metrics.nPdbAlloc );
}
}
if( rv ){
rv->pNext = SJG.perDb.aUsed;
SJG.perDb.aUsed = rv;
if(rv->pNext){
assert(!rv->pNext->pPrev);
rv->pNext->pPrev = rv;
}
rv->jDb = REF_G(jDb);
rv->pDb = pDb;
}
MUTEX_PDB_LEAVE;
return rv;
}
/*
** Returns the S3JniDb object for the given db.
**
** The 3rd argument should normally only be non-0 for routines which
** are called from the C library and pass a native db handle instead of
** a Java handle. In normal usage, the 2nd argument is a Java-side sqlite3
** object, from which the db is fished out.
**
** Returns NULL if jDb and pDb are both NULL or if there is no
** matching S3JniDb entry for pDb or the pointer fished out of jDb.
*/
static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){
S3JniDb * s = 0;
if(jDb || pDb){
MUTEX_PDB_ENTER;
s = SJG.perDb.aUsed;
if(!pDb){
assert( jDb );
pDb = PtrGet_sqlite3(jDb);
}
for( ; pDb && s; s = s->pNext){
if(s->pDb == pDb){
break;
}
}
MUTEX_PDB_LEAVE;
}
return s;
}
/*
** Unref any Java-side state in ax and zero out ax.
*/
static void S3JniAutoExtension_clear(JNIEnv * const env,
S3JniAutoExtension * const ax){
if( ax->jObj ){
UNREF_G(ax->jObj);
memset(ax, 0, sizeof(*ax));
}
}
/*
** Initializes a pre-allocated S3JniAutoExtension object. Returns
** non-0 if there is an error collecting the required state from
** jAutoExt (which must be an AutoExtension object). On error, it
** passes ax to S3JniAutoExtension_clear().
*/
static int S3JniAutoExtension_init(JNIEnv *const env,
S3JniAutoExtension * const ax,
jobject const jAutoExt){
jclass const klazz = (*env)->GetObjectClass(env, jAutoExt);
ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint",
"(Lorg/sqlite/jni/sqlite3;)I");
UNREF_L(klazz);
EXCEPTION_WARN_IGNORE;
if(!ax->midFunc){
MARKER(("Error getting xEntryPoint(sqlite3) from AutoExtension object."));
S3JniAutoExtension_clear(env, ax);
return SQLITE_ERROR;
}
ax->jObj = REF_G(jAutoExt);
return 0;
}
/*
** Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
** This function calls sqlite3_aggregate_context() to allocate a tiny
** sliver of memory, the address of which is set in
** jCx->aggregateContext. The memory is only used as a key for
** mapping client-side results of aggregate result sets across
** calls to the UDF's callbacks.
**
** isFinal must be 1 for xFinal() calls and 0 for all others, the
** difference being that the xFinal() invocation will not allocate
** new memory if it was not already, resulting in a value of 0
** for jCx->aggregateContext.
**
** Returns 0 on success. Returns SQLITE_NOMEM on allocation error,
** noting that it will not allocate when isFinal is true. It returns
** SQLITE_ERROR if there's a serious internal error in dealing with
** the JNI state.
*/
static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
sqlite3_context * pCx,
int isFinal){
void * pAgg;
int rc = 0;
S3JniNphClass * const pNC =
S3JniGlobal_nph_cache(env, &S3NphRefs.sqlite3_context);
if(!pNC->fidAggCtx){
MUTEX_NPH_ENTER;
if(!pNC->fidAggCtx){
pNC->fidAggCtx = (*env)->GetFieldID(env, pNC->klazz, "aggregateContext", "J");
EXCEPTION_IS_FATAL("Cannot get sqlite3_contex.aggregateContext member.");
}
MUTEX_NPH_LEAVE;
}
pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : sizeof(void*));
if( pAgg || isFinal ){
(*env)->SetLongField(env, jCx, pNC->fidAggCtx, (jlong)pAgg);
}else{
assert(!pAgg);
rc = SQLITE_NOMEM;
}
return rc;
}
/*
** Common init for OutputPointer_set_Int32() and friends. pRef must be
** a pointer from S3NphRefs. jOut must be an instance of that
** class. If necessary, this fetches the jfieldID for jOut's [value]
** property, which must be of the type represented by the JNI type
** signature zTypeSig, and stores it in pRef's S3JniGlobal.nph entry.
** Fails fatally if the property is not found, as that presents a
** serious internal misuse.
**
** Property lookups are cached on a per-pRef basis. Do not use this
** routine with the same pRef but different zTypeSig: it will
** misbehave.
*/
static jfieldID setupOutputPointer(JNIEnv * const env, S3NphRef const * pRef,
const char * const zTypeSig,
jobject const jOut){
S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef);
if(!pNC->fidValue){
MUTEX_NPH_ENTER;
if(!pNC->fidValue){
pNC->fidValue = (*env)->GetFieldID(env, pNC->klazz, "value", zTypeSig);
EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value");
}
MUTEX_NPH_LEAVE;
}
return pNC->fidValue;
}
/*
** Sets the value property of the OutputPointer.Int32 jOut object to
** v.
*/
static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_Int32, "I", jOut
);
(*env)->SetIntField(env, jOut, setter, (jint)v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value");
}
/*
** Sets the value property of the OutputPointer.Int64 jOut object to
** v.
*/
static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_Int64, "J", jOut
);
(*env)->SetLongField(env, jOut, setter, v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value");
}
/*
** Sets the value property of the OutputPointer.sqlite3 jOut object to
** v.
*/
static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut,
jobject jDb){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut
);
(*env)->SetObjectField(env, jOut, setter, jDb);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value");
}
/*
** Sets the value property of the OutputPointer.sqlite3_stmt jOut object to
** v.
*/
static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut,
jobject jStmt){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_sqlite3_stmt,
"Lorg/sqlite/jni/sqlite3_stmt;", jOut
);
(*env)->SetObjectField(env, jOut, setter, jStmt);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value");
}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
/*
** Sets the value property of the OutputPointer.sqlite3_value jOut object to
** v.
*/
static void OutputPointer_set_sqlite3_value(JNIEnv * const env, jobject const jOut,
jobject jValue){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_sqlite3_value,
"Lorg/sqlite/jni/sqlite3_value;", jOut
);
(*env)->SetObjectField(env, jOut, setter, jValue);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_value.value");
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
#ifdef SQLITE_ENABLE_FTS5
#if 0
/*
** Sets the value property of the OutputPointer.ByteArray jOut object
** to v.
*/
static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut,
jbyteArray const v){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_ByteArray, "[B", jOut
);
(*env)->SetObjectField(env, jOut, setter, v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value");
}
#endif
/*
** Sets the value property of the OutputPointer.String jOut object to
** v.
*/
static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut,
jstring const v){
jfieldID const setter = setupOutputPointer(
env, &S3NphRefs.OutputPointer_String, "Ljava/lang/String;", jOut
);
(*env)->SetObjectField(env, jOut, setter, v);
EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value");
}
#endif /* SQLITE_ENABLE_FTS5 */
/*
** Returns true if eTextRep is a valid sqlite3 encoding constant, else
** returns false.
*/
static int encodingTypeIsValid(int eTextRep){
switch(eTextRep){
case SQLITE_UTF8: case SQLITE_UTF16:
case SQLITE_UTF16LE: case SQLITE_UTF16BE:
return 1;
default:
return 0;
}
}
/*
** Proxy for Java-side Collation.xCompare() callbacks.
*/
static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs,
int nRhs, const void *rhs){
S3JniDb * const ps = pArg;
LocalJniGetEnv;
jint rc = 0;
jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs);
jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL;
if(!jbaRhs){
UNREF_L(jbaLhs);
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
return 0;
}
(*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs);
(*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs);
rc = (*env)->CallIntMethod(env, ps->hooks.collation.jObj,
ps->hooks.collation.midCallback,
jbaLhs, jbaRhs);
EXCEPTION_IGNORE;
UNREF_L(jbaLhs);
UNREF_L(jbaRhs);
return (int)rc;
}
/* Collation finalizer for use by the sqlite3 internals. */
static void CollationState_xDestroy(void *pArg){
S3JniDb * const ps = pArg;
S3JniHook_unref( s3jni_get_env(), &ps->hooks.collation, 1 );
}
/*
** 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. OTOH, we might
** find more state to pack in here.
*/
typedef struct {
jobject jObj;
} ResultJavaVal;
/* For use with sqlite3_result/value_pointer() */
#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal"
/*
** Allocate a new ResultJavaVal and assign it a new global ref of
** jObj. Caller owns the returned object and must eventually pass it
** to ResultJavaVal_finalizer().
*/
static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){
ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal));
if(rv){
rv->jObj = jObj ? REF_G(jObj) : 0;
}
return rv;
}
/*
** If v is not NULL, it must point to a a ResultJavaVal object. Its
** object reference is relinquished and v is freed.
*/
static void ResultJavaVal_finalizer(void *v){
if(v){
ResultJavaVal * const rv = (ResultJavaVal*)v;
LocalJniGetEnv;
UNREF_G(rv->jObj);
sqlite3_free(rv);
}
}
/*
** Returns a new Java instance of the class named by zClassName, which
** MUST be interface-compatible with NativePointerHolder and MUST have
** a no-arg constructor. The NativePointerHolder_set() method is
** passed the new Java object and pNative. Hypothetically returns NULL
** if Java fails to allocate, but the JNI docs are not entirely clear
** on that detail.
**
** Always use a static pointer from the S3NphRefs struct for the 2nd
** argument so that we can use pRef->index as an O(1) cache key.
*/
static jobject new_NativePointerHolder_object(JNIEnv * const env, S3NphRef const * pRef,
const void * pNative){
jobject rv = 0;
S3JniNphClass * const pNC = S3JniGlobal_nph_cache(env, pRef);
if(!pNC->midCtor){
MUTEX_NPH_ENTER;
if(!pNC->midCtor){
pNC->midCtor = (*env)->GetMethodID(env, pNC->klazz, "<init>", "()V");
EXCEPTION_IS_FATAL("Cannot find constructor for class.");
}
MUTEX_NPH_LEAVE;
}
rv = (*env)->NewObject(env, pNC->klazz, pNC->midCtor);
EXCEPTION_IS_FATAL("No-arg constructor threw.");
s3jni_oom_check(rv);
if(rv) NativePointerHolder_set(env, rv, pNative, pRef);
return rv;
}
static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){
return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3, sv);
}
static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){
return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_context, sv);
}
static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){
return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_stmt, sv);
}
static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){
return new_NativePointerHolder_object(env, &S3NphRefs.sqlite3_value, sv);
}
/*
** Type IDs for SQL function categories.
*/
enum UDFType {
UDF_SCALAR = 1,
UDF_AGGREGATE,
UDF_WINDOW,
UDF_UNKNOWN_TYPE/*for error propagation*/
};
typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**);
typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**);
typedef void (*udf_xFinal_f)(sqlite3_context*);
/*typedef void (*udf_xValue_f)(sqlite3_context*);*/
/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/
/**
State for binding Java-side UDFs.
*/
typedef struct S3JniUdf S3JniUdf;
struct S3JniUdf {
jobject jObj /* SQLFunction instance */;
char * zFuncName /* Only for error reporting and debug logging */;
enum UDFType type;
/** Method IDs for the various UDF methods. */
jmethodID jmidxFunc;
jmethodID jmidxStep;
jmethodID jmidxFinal;
jmethodID jmidxValue;
jmethodID jmidxInverse;
};
static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){
S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf));
if(s){
const char * zFSI = /* signature for xFunc, xStep, xInverse */
"(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V";
const char * zFV = /* signature for xFinal, xValue */
"(Lorg/sqlite/jni/sqlite3_context;)V";
jclass const klazz = (*env)->GetObjectClass(env, jObj);
memset(s, 0, sizeof(S3JniUdf));
s->jObj = REF_G(jObj);
#define FGET(FuncName,FuncType,Field) \
s->Field = (*env)->GetMethodID(env, klazz, FuncName, FuncType); \
if(!s->Field) (*env)->ExceptionClear(env)
FGET("xFunc", zFSI, jmidxFunc);
FGET("xStep", zFSI, jmidxStep);
FGET("xFinal", zFV, jmidxFinal);
FGET("xValue", zFV, jmidxValue);
FGET("xInverse", zFSI, jmidxInverse);
#undef FGET
UNREF_L(klazz);
if(s->jmidxFunc) s->type = UDF_SCALAR;
else if(s->jmidxStep && s->jmidxFinal){
s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE;
}else{
s->type = UDF_UNKNOWN_TYPE;
}
}
return s;
}
static void S3JniUdf_free(S3JniUdf * s){
LocalJniGetEnv;
if(env){
//MARKER(("UDF cleanup: %s\n", s->zFuncName));
s3jni_call_xDestroy(env, s->jObj);
UNREF_G(s->jObj);
}
sqlite3_free(s->zFuncName);
sqlite3_free(s);
}
static void S3JniUdf_finalizer(void * s){
//MARKER(("UDF finalizer @ %p\n", s));
if(s) S3JniUdf_free((S3JniUdf*)s);
}
/**
Helper for processing args to UDF handlers
with signature (sqlite3_context*,int,sqlite3_value**).
*/
typedef struct {
jobject jcx;
jobjectArray jargv;
} udf_jargs;
/**
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.
*/
static int udf_args(JNIEnv *env,
sqlite3_context * const cx,
int argc, sqlite3_value**argv,
jobject * jCx, jobjectArray *jArgv){
jobjectArray ja = 0;
jobject jcx = new_sqlite3_context_wrapper(env, cx);
jint i;
*jCx = 0;
*jArgv = 0;
if(!jcx) goto error_oom;
ja = (*env)->NewObjectArray(env, argc,
SJG.g.cObj,
NULL);
if(!ja) goto error_oom;
for(i = 0; i < argc; ++i){
jobject jsv = new_sqlite3_value_wrapper(env, argv[i]);
if(!jsv) goto error_oom;
(*env)->SetObjectArrayElement(env, ja, i, jsv);
UNREF_L(jsv)/*array has a ref*/;
}
*jCx = jcx;
*jArgv = ja;
return 0;
error_oom:
sqlite3_result_error_nomem(cx);
UNREF_L(jcx);
UNREF_L(ja);
return SQLITE_NOMEM;
}
/*
** 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{
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;
}
/*
** 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* const pCx, int argc,
sqlite3_value** const argv,
S3JniUdf * const s,
jmethodID xMethodID,
const char * const zFuncType){
LocalJniGetEnv;
udf_jargs args = {0,0};
int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
//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(env, 'F'==zFuncType[1]/*xFunc*/, pCx,
s->zFuncName, zFuncType);
}
}
UNREF_L(args.jcx);
UNREF_L(args.jargv);
return rc;
}
/*
** Sets up the state for calling a Java-side xFinal/xValue() UDF,
** calls it, and returns 0 on success.
*/
static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
jmethodID xMethodID,
const char *zFuncType){
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){
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, isFinal);
}
if( 0 == rc ){
(*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
IFTHREW{
rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
zFuncType);
}
}
UNREF_L(jcx);
return rc;
}
/* Proxy for C-to-Java xFunc. */
static void udf_xFunc(sqlite3_context* cx, int argc,
sqlite3_value** argv){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
s3jni_incr( &SJG.metrics.udf.nFunc );
udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc");
}
/* Proxy for C-to-Java xStep. */
static void udf_xStep(sqlite3_context* cx, int argc,
sqlite3_value** argv){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
s3jni_incr( &SJG.metrics.udf.nStep );
udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep");
}
/* Proxy for C-to-Java xFinal. */
static void udf_xFinal(sqlite3_context* cx){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
s3jni_incr( &SJG.metrics.udf.nFinal );
udf_xFV(cx, s, s->jmidxFinal, "xFinal");
}
/* Proxy for C-to-Java xValue. */
static void udf_xValue(sqlite3_context* cx){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
s3jni_incr( &SJG.metrics.udf.nValue );
udf_xFV(cx, s, s->jmidxValue, "xValue");
}
/* Proxy for C-to-Java xInverse. */
static void udf_xInverse(sqlite3_context* cx, int argc,
sqlite3_value** argv){
S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx);
s3jni_incr( &SJG.metrics.udf.nInverse );
udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse");
}
////////////////////////////////////////////////////////////////////////
// What follows is the JNI/C bindings. They are in alphabetical order
// except for this macro-generated subset which are kept together
// (alphabetized) here at the front...
////////////////////////////////////////////////////////////////////////
WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count)
WRAP_INT_DB(1changes, sqlite3_changes)
WRAP_INT64_DB(1changes64, sqlite3_changes64)
WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings)
WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes)
WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16)
WRAP_INT_STMT(1column_1count, sqlite3_column_count)
WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype)
WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name)
WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name)
WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name)
WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name)
WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type)
WRAP_INT_STMT(1data_1count, sqlite3_data_count)
WRAP_INT_DB(1error_1offset, sqlite3_error_offset)
WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode)
WRAP_MUTF8_VOID(1libversion, sqlite3_libversion)
WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number)
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
WRAP_INT_DB(1preupdate_1blobwrite, sqlite3_preupdate_blobwrite)
WRAP_INT_DB(1preupdate_1count, sqlite3_preupdate_count)
WRAP_INT_DB(1preupdate_1depth, sqlite3_preupdate_depth)
#endif
WRAP_INT_INT(1sleep, sqlite3_sleep)
WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid)
WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe)
WRAP_INT_DB(1total_1changes, sqlite3_total_changes)
WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64)
WRAP_INT_SVALUE(1value_1bytes, sqlite3_value_bytes)
WRAP_INT_SVALUE(1value_1bytes16, sqlite3_value_bytes16)
WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding)
WRAP_INT_SVALUE(1value_1frombind, sqlite3_value_frombind)
WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange)
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)
#undef WRAP_INT64_DB
#undef WRAP_INT_DB
#undef WRAP_INT_INT
#undef WRAP_INT_STMT
#undef WRAP_INT_STMT_INT
#undef WRAP_INT_SVALUE
#undef WRAP_INT_VOID
#undef WRAP_MUTF8_VOID
#undef WRAP_STR_STMT_INT
/* Central auto-extension handler. */
static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
const struct sqlite3_api_routines *ignored){
int rc = 0;
unsigned i, go = 1;
JNIEnv * env = 0;
S3JniDb * ps;
S3JniEnv * jc;
if( 0==SJG.autoExt.nExt ) return 0;
env = s3jni_get_env();
jc = S3JniGlobal_env_cache(env);
ps = jc->pdbOpening;
if( !ps ){
MARKER(("Unexpected arrival of null S3JniDb in auto-extension runner.\n"));
*pzErr = sqlite3_mprintf("Unexpected arrival of null S3JniDb in auto-extension runner.");
return SQLITE_ERROR;
}
jc->pdbOpening = 0;
assert( !ps->pDb && "it's still being opened" );
ps->pDb = pDb;
assert( ps->jDb );
NativePointerHolder_set(env, ps->jDb, pDb, &S3NphRefs.sqlite3);
for( i = 0; go && 0==rc; ++i ){
S3JniAutoExtension ax = {0,0}
/* We need a copy of the auto-extension object, with our own
** local reference to it, to avoid a race condition with another
** thread manipulating the list during the call and invaliding
** what ax points to. */;
MUTEX_EXT_ENTER;
if( i >= SJG.autoExt.nExt ){
go = 0;
}else{
ax.jObj = REF_L(SJG.autoExt.pExt[i].jObj);
ax.midFunc = SJG.autoExt.pExt[i].midFunc;
}
MUTEX_EXT_LEAVE;
if( ax.jObj ){
rc = (*env)->CallIntMethod(env, ax.jObj, ax.midFunc, ps->jDb);
IFTHREW {
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);
if( !rc ) rc = SQLITE_ERROR;
}
UNREF_L(ax.jObj);
}
}
return rc;
}
JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
static int once = 0;
int i;
S3JniAutoExtension * ax;
int rc = 0;
if( !jAutoExt ) return SQLITE_MISUSE;
MUTEX_EXT_ENTER;
for( i = 0; i < SJG.autoExt.nExt; ++i ){
/* Look for match or first empty slot. */
ax = &SJG.autoExt.pExt[i];
if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
MUTEX_EXT_LEAVE;
return 0 /* this as a no-op. */;
}
}
if(i == SJG.autoExt.nExt ){
assert( SJG.autoExt.nExt <= SJG.autoExt.nAlloc );
if( SJG.autoExt.nExt == SJG.autoExt.nAlloc ){
unsigned n = 1 + SJG.autoExt.nAlloc;
S3JniAutoExtension * const aNew =
sqlite3_realloc( SJG.autoExt.pExt,
n * sizeof(S3JniAutoExtension) );
if( !aNew ){
rc = SQLITE_NOMEM;
}else{
SJG.autoExt.pExt = aNew;
++SJG.autoExt.nAlloc;
}
}
if( 0==rc ){
ax = &SJG.autoExt.pExt[SJG.autoExt.nExt];
rc = S3JniAutoExtension_init(env, ax, jAutoExt);
assert( rc ? 0==ax->jObj : 0!=ax->jObj );
}
}
if( 0==rc ){
if( 0==once && ++once ){
rc = sqlite3_auto_extension( (void(*)(void))s3jni_run_java_auto_extensions );
if( rc ){
assert( ax );
S3JniAutoExtension_clear(env, ax);
}
}
if( 0==rc ){
++SJG.autoExt.nExt;
}
}
MUTEX_EXT_LEAVE;
return rc;
}
JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0;
int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
s3jni_jbytearray_release(baData,pBuf);
return (jint)rc;
}
JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt,
jint ndx, jdouble val){
return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val);
}
JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt,
jint ndx, jint val){
return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val);
}
JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt,
jint ndx, jlong val){
return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val);
}
JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){
int rc = 0;
jbyte * const pBuf = s3jni_jbytearray_bytes(jName);
if(pBuf){
rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt),
(const char *)pBuf);
s3jni_jbytearray_release(jName, pBuf);
}
return rc;
}
JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0;
int const rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
(const char *)pBuf,
(int)nMax, SQLITE_TRANSIENT);
s3jni_jbytearray_release(baData, pBuf);
return (jint)rc;
}
JDECL(jint,1bind_1text16)(JENV_CSELF, jobject jpStmt,
jint ndx, jbyteArray baData, jint nMax){
jbyte * const pBuf = baData ? s3jni_jbytearray_bytes(baData) : 0;
int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
pBuf, (int)nMax, SQLITE_TRANSIENT);
s3jni_jbytearray_release(baData, pBuf);
return (jint)rc;
}
JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt,
jint ndx, jint n){
return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n);
}
JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt,
jint ndx, jlong n){
return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n);
}
/* Central C-to-Java busy handler proxy. */
static int s3jni_busy_handler(void* pState, int n){
S3JniDb * const ps = (S3JniDb *)pState;
int rc = 0;
if( ps->hooks.busyHandler.jObj ){
LocalJniGetEnv;
rc = (*env)->CallIntMethod(env, ps->hooks.busyHandler.jObj,
ps->hooks.busyHandler.midCallback, (jint)n);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("sqlite3_busy_handler() callback");
rc = s3jni_db_exception(env, ps, SQLITE_ERROR,
"sqlite3_busy_handler() callback threw.");
}
}
return rc;
}
JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
int rc = 0;
if(!ps) return (jint)SQLITE_NOMEM;
if(jBusy){
S3JniHook * const pHook = &ps->hooks.busyHandler;
if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){
/* Same object - this is a no-op. */
return 0;
}
jclass klazz;
S3JniHook_unref(env, pHook, 1);
pHook->jObj = REF_G(jBusy);
klazz = (*env)->GetObjectClass(env, jBusy);
pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(I)I");
UNREF_L(klazz);
IFTHREW {
S3JniHook_unref(env, pHook, 0);
rc = SQLITE_ERROR;
}
if(rc){
return rc;
}
}else{
S3JniHook_unref(env, &ps->hooks.busyHandler, 1);
}
return jBusy
? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps)
: sqlite3_busy_handler(ps->pDb, 0, 0);
}
JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
if( ps ){
S3JniHook_unref(env, &ps->hooks.busyHandler, 1);
return sqlite3_busy_timeout(ps->pDb, (int)ms);
}
return SQLITE_MISUSE;
}
JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){
S3JniAutoExtension * ax;
jboolean rc = JNI_FALSE;
int i;
MUTEX_EXT_ENTER;
/* This algo mirrors the one in the core. */
for( i = SJG.autoExt.nExt-1; i >= 0; --i ){
ax = &SJG.autoExt.pExt[i];
if( ax->jObj && (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){
S3JniAutoExtension_clear(env, ax);
/* Move final entry into this slot. */
--SJG.autoExt.nExt;
*ax = SJG.autoExt.pExt[SJG.autoExt.nExt];
memset(&SJG.autoExt.pExt[SJG.autoExt.nExt], 0,
sizeof(S3JniAutoExtension));
assert(! SJG.autoExt.pExt[SJG.autoExt.nExt].jObj );
rc = JNI_TRUE;
break;
}
}
MUTEX_EXT_LEAVE;
return rc;
}
/* Wrapper for sqlite3_close(_v2)(). */
static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){
int rc = 0;
S3JniDb * ps = 0;
assert(version == 1 || version == 2);
ps = S3JniDb_for_db(env, jDb, 0);
if(ps){
rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb);
if( 0==rc ){
S3JniDb_set_aside(env, ps)
/* MUST come after close() because of ps->trace. */;
NativePointerHolder_set(env, jDb, 0, &S3NphRefs.sqlite3);
}
}
return (jint)rc;
}
JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){
return s3jni_close_db(env, pDb, 2);
}
JDECL(jint,1close)(JENV_CSELF, jobject pDb){
return s3jni_close_db(env, pDb, 1);
}
/*
** Assumes z is an array of unsigned short and returns the index in
** that array of the first element with the value 0.
*/
static unsigned int s3jni_utf16_strlen(void const * z){
unsigned int i = 0;
const unsigned short * p = z;
while( p[i] ) ++i;
return i;
}
/* Central C-to-Java sqlite3_collation_needed16() hook impl. */
static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb,
int eTextRep, const void * z16Name){
S3JniDb * const ps = pState;
LocalJniGetEnv;
unsigned int const nName = s3jni_utf16_strlen(z16Name);
jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName);
IFTHREW{
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
EXCEPTION_CLEAR;
}else{
(*env)->CallVoidMethod(env, ps->hooks.collationNeeded.jObj,
ps->hooks.collationNeeded.midCallback,
ps->jDb, (jint)eTextRep, jName);
IFTHREW{
s3jni_db_exception(env, ps, 0, "sqlite3_collation_needed() callback threw");
}
UNREF_L(jName);
}
}
JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
jobject pOld = 0;
jmethodID xCallback;
S3JniHook * const pHook = &ps->hooks.collationNeeded;
int rc;
if( !ps ) return SQLITE_MISUSE;
pOld = pHook->jObj;
if(pOld && jHook &&
(*env)->IsSameObject(env, pOld, jHook)){
return 0;
}
if( !jHook ){
UNREF_G(pOld);
memset(pHook, 0, sizeof(S3JniHook));
sqlite3_collation_needed(ps->pDb, 0, 0);
return 0;
}
klazz = (*env)->GetObjectClass(env, jHook);
xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded",
"(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I");
UNREF_L(klazz);
IFTHREW {
rc = s3jni_db_exception(env, ps, SQLITE_MISUSE,
"Cannot not find matching callback on "
"collation-needed hook object.");
}else{
pHook->midCallback = xCallback;
pHook->jObj = REF_G(jHook);
UNREF_G(pOld);
rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16);
}
return rc;
}
JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
void const * const p = sqlite3_column_blob(pStmt, (int)ndx);
int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0;
if( 0==p ) return NULL;
else{
jbyteArray const jba = (*env)->NewByteArray(env, n);
(*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p);
return jba;
}
}
JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt,
jint ndx){
return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
JDECL(jbyteArray,1column_1text_1utf8)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
const int n = sqlite3_column_bytes(stmt, (int)ndx);
const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx);
return p ? s3jni_new_jbyteArray(env, p, n) : NULL;
}
JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt);
const int n = sqlite3_column_bytes16(stmt, (int)ndx);
const void * const p = sqlite3_column_text16(stmt, (int)ndx);
return s3jni_text16_to_jstring(env, p, n);
}
JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt,
jint ndx){
sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
return new_sqlite3_value_wrapper(env, sv);
}
static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){
LocalJniGetEnv;
int rc = isCommit
? (int)(*env)->CallIntMethod(env, ps->hooks.commit.jObj,
ps->hooks.commit.midCallback)
: (int)((*env)->CallVoidMethod(env, ps->hooks.rollback.jObj,
ps->hooks.rollback.midCallback), 0);
IFTHREW{
EXCEPTION_CLEAR;
rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw.");
}
return rc;
}
static int s3jni_commit_hook_impl(void *pP){
return s3jni_commit_rollback_hook_impl(1, pP);
}
static void s3jni_rollback_hook_impl(void *pP){
(void)s3jni_commit_rollback_hook_impl(0, pP);
}
static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,
jobject jDb, jobject jHook){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
jobject pOld = 0;
jmethodID xCallback;
S3JniHook * const pHook =
isCommit ? &ps->hooks.commit : &ps->hooks.rollback;
if(!ps){
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
return 0;
}
pOld = pHook->jObj;
if(pOld && jHook &&
(*env)->IsSameObject(env, pOld, jHook)){
return pOld;
}
if( !jHook ){
if(pOld){
jobject tmp = REF_L(pOld);
UNREF_G(pOld);
pOld = tmp;
}
memset(pHook, 0, sizeof(S3JniHook));
if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0);
else sqlite3_rollback_hook(ps->pDb, 0, 0);
return pOld;
}
klazz = (*env)->GetObjectClass(env, jHook);
xCallback = (*env)->GetMethodID(env, klazz,
isCommit ? "xCommitHook" : "xRollbackHook",
isCommit ? "()I" : "()V");
UNREF_L(klazz);
IFTHREW {
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching callback on "
"hook object.");
}else{
pHook->midCallback = xCallback;
pHook->jObj = REF_G(jHook);
if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps);
else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps);
if(pOld){
jobject tmp = REF_L(pOld);
UNREF_G(pOld);
pOld = tmp;
}
}
return pOld;
}
JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){
return s3jni_commit_rollback_hook(1, env, jDb, jHook);
}
JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){
return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) )
/* We know these to be ASCII, so MUTF-8 is fine. */;
}
JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){
const char *zUtf8 = s3jni_jstring_to_mutf8(name);
const jboolean rc =
0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE;
s3jni_mutf8_release(name, zUtf8);
return rc;
}
/*
** sqlite3_config(SQLITE_CONFIG_...) wrapper for a small subset of
** options.
*/
JDECL(jint,1config__I)(JENV_CSELF, jint n){
switch(n){
case SQLITE_CONFIG_SINGLETHREAD:
case SQLITE_CONFIG_MULTITHREAD:
case SQLITE_CONFIG_SERIALIZED:
return sqlite3_config( n );
default:
return SQLITE_MISUSE;
}
}
#ifdef SQLITE_ENABLE_SQLLOG
/* C-to-Java SQLITE_CONFIG_SQLLOG wrapper. */
static void s3jni_config_sqllog(void *ignored, sqlite3 *pDb, const char *z, int op){
jobject jArg0 = 0;
jstring jArg1 = 0;
LocalJniGetEnv;
S3JniDb * const ps = S3JniDb_for_db(env, 0, pDb);
S3JniHook * const hook = &SJG.hooks.sqllog;
if( !ps || !hook->jObj ) return;
jArg0 = REF_L(ps->jDb);
switch(op){
case 0: /* db opened */
case 1: /* SQL executed */
jArg1 = s3jni_utf8_to_jstring(env, z, -1);
break;
case 2: /* db closed */
break;
default:
(*env)->FatalError(env, "Unhandled 4th arg to SQLITE_CONFIG_SQLLOG.");
break;
}
(*env)->CallVoidMethod(env, hook->jObj, hook->midCallback, jArg0, jArg1, op);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("SQLITE_CONFIG_SQLLOG callback");
EXCEPTION_CLEAR;
}
UNREF_L(jArg0);
UNREF_L(jArg1);
}
//! Requirement of SQLITE_CONFIG_SQLLOG.
void sqlite3_init_sqllog(void){
sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
}
#endif
/* sqlite3_config(SQLITE_CONFIG_SQLLOG) wrapper. */
JDECL(jint,1config__Lorg_sqlite_jni_SQLLog_2)(JENV_CSELF, jobject jLog){
#ifdef SQLITE_ENABLE_SQLLOG
S3JniHook tmpHook;
S3JniHook * const hook = &tmpHook;
S3JniHook * const hookOld = & SJG.hooks.sqllog;
jclass klazz;
int rc = 0;
if( !jLog ){
S3JniHook_unref(env, hookOld, 0);
return 0;
}
if( hookOld->jObj && (*env)->IsSameObject(env, jLog, hookOld->jObj) ){
return 0;
}
klazz = (*env)->GetObjectClass(env, jLog);
hook->midCallback = (*env)->GetMethodID(env, klazz, "xSqllog",
"(Lorg/sqlite/jni/sqlite3;"
"Ljava/lang/String;"
"I)V");
UNREF_L(klazz);
if( !hook->midCallback ){
EXCEPTION_WARN_IGNORE;
S3JniHook_unref(env, hook, 0);
return SQLITE_ERROR;
}
hook->jObj = REF_G(jLog);
rc = sqlite3_config( SQLITE_CONFIG_SQLLOG, s3jni_config_sqllog, 0 );
if( rc ){
S3JniHook_unref(env, hook, 0);
}else{
S3JniHook_unref(env, hookOld, 0);
*hookOld = *hook;
}
return rc;
#else
MARKER(("Warning: built without SQLITE_ENABLE_SQLLOG.\n"));
return SQLITE_MISUSE;
#endif
}
JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){
sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx));
S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb) : 0;
return ps ? ps->jDb : 0;
}
JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb,
jstring name, jint eTextRep,
jobject oCollation){
int rc;
const char *zName;
jclass klazz;
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
S3JniHook * const pHook = ps ? &ps->hooks.collation : 0;
if( !pHook ) return SQLITE_MISUSE;
klazz = (*env)->GetObjectClass(env, oCollation);
pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare",
"([B[B)I");
UNREF_L(klazz);
IFTHREW{
UNREF_L(klazz);
return s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Could not get xCompare() method for object.");
}
zName = s3jni_jstring_to_mutf8(name);
rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep,
ps, CollationState_xCompare,
CollationState_xDestroy);
s3jni_mutf8_release(name, zName);
if( 0==rc ){
pHook->jObj = REF_G(oCollation);
}else{
S3JniHook_unref(env, pHook, 1);
}
return (jint)rc;
}
JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName,
jint nArg, jint eTextRep, jobject jFunctor){
S3JniUdf * s = 0;
int rc;
sqlite3 * const pDb = PtrGet_sqlite3(jDb);
char * zFuncName = 0;
if( !encodingTypeIsValid(eTextRep) ){
return s3jni_db_error(pDb, SQLITE_FORMAT,
"Invalid function encoding option.");
}
s = S3JniUdf_alloc(env, jFunctor);
if( !s ) return SQLITE_NOMEM;
else if( UDF_UNKNOWN_TYPE==s->type ){
rc = s3jni_db_error(pDb, SQLITE_MISUSE,
"Cannot unambiguously determine function type.");
S3JniUdf_free(s);
goto error_cleanup;
}
zFuncName = s3jni_jstring_to_utf8(env,jFuncName,0);
if(!zFuncName){
rc = SQLITE_NOMEM;
S3JniUdf_free(s);
goto error_cleanup;
}
if( UDF_WINDOW == s->type ){
rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s,
udf_xStep, udf_xFinal, udf_xValue,
udf_xInverse, S3JniUdf_finalizer);
}else{
udf_xFunc_f xFunc = 0;
udf_xStep_f xStep = 0;
udf_xFinal_f xFinal = 0;
if( UDF_SCALAR == s->type ){
xFunc = udf_xFunc;
}else{
assert( UDF_AGGREGATE == s->type );
xStep = udf_xStep;
xFinal = udf_xFinal;
}
rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s,
xFunc, xStep, xFinal, S3JniUdf_finalizer);
}
error_cleanup:
sqlite3_free(zFuncName);
/* on sqlite3_create_function() error, s will be destroyed via
** create_function(), so we're not leaking s. */
return (jint)rc;
}
/* sqlite3_db_config() for (int,const char *) */
JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)(
JENV_CSELF, jobject jDb, jint op, jstring jStr
){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
int rc;
char *zStr;
switch( (ps && jStr) ? op : 0 ){
case SQLITE_DBCONFIG_MAINDBNAME:
zStr = s3jni_jstring_to_utf8(env, jStr, 0);
if( zStr ){
rc = sqlite3_db_config(ps->pDb, (int)op, zStr);
if( rc ){
sqlite3_free( zStr );
}else{
sqlite3_free( ps->zMainDbName );
ps->zMainDbName = zStr;
}
}else{
rc = SQLITE_NOMEM;
}
break;
default:
rc = SQLITE_MISUSE;
}
return rc;
}
/* sqlite3_db_config() for (int,int*) */
/*
** ACHTUNG: openjdk v19 creates a different mangled name for this
** function than openjdk v8 does. We account for that by exporting
** both versions of the name.
*/
JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)(
JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut
){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
int rc;
switch( ps ? op : 0 ){
case SQLITE_DBCONFIG_ENABLE_FKEY:
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
case SQLITE_DBCONFIG_ENABLE_QPSG:
case SQLITE_DBCONFIG_TRIGGER_EQP:
case SQLITE_DBCONFIG_RESET_DATABASE:
case SQLITE_DBCONFIG_DEFENSIVE:
case SQLITE_DBCONFIG_WRITABLE_SCHEMA:
case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE:
case SQLITE_DBCONFIG_DQS_DML:
case SQLITE_DBCONFIG_DQS_DDL:
case SQLITE_DBCONFIG_ENABLE_VIEW:
case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT:
case SQLITE_DBCONFIG_TRUSTED_SCHEMA:
case SQLITE_DBCONFIG_STMT_SCANSTATUS:
case SQLITE_DBCONFIG_REVERSE_SCANORDER: {
int pOut = 0;
rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut );
if( 0==rc && jOut ){
OutputPointer_set_Int32(env, jOut, pOut);
}
break;
}
default:
rc = SQLITE_MISUSE;
}
return (jint)rc;
}
/*
** This is a workaround for openjdk v19 (and possibly others) encoding
** this function's name differently than JDK v8 does. If we do not
** install both names for this function then Java will not be able to
** find the function in both environments.
*/
JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)(
JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut
){
return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)(
env, jKlazz, jDb, op, onOff, jOut
);
}
JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
char *zDbName;
jstring jRv = 0;
int nStr = 0;
if( !ps || !jDbName ){
return 0;
}
zDbName = s3jni_jstring_to_utf8(env, jDbName, &nStr);
if( zDbName ){
char const * zRv = sqlite3_db_filename(ps->pDb, zDbName);
sqlite3_free(zDbName);
if( zRv ){
jRv = s3jni_utf8_to_jstring(env, zRv, -1);
}
}
return jRv;
}
JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent,
jobject jOutHigh, jboolean reset ){
int iCur = 0, iHigh = 0;
sqlite3 * const pDb = PtrGet_sqlite3(jDb);
int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset );
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCurrent, iCur);
OutputPointer_set_Int32(env, jOutHigh, iHigh);
}
return (jint)rc;
}
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){
sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
return pDb ? s3jni_utf8_to_jstring(env, sqlite3_errmsg(pDb), -1) : 0;
}
JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){
return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
/* We know these values to be plain ASCII, so pose no MUTF-8
** incompatibility */;
}
JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
jstring rv = 0;
if( pStmt ){
char * zSql = sqlite3_expanded_sql(pStmt);
s3jni_oom_check(zSql);
if( zSql ){
rv = s3jni_utf8_to_jstring(env, zSql, -1);
sqlite3_free(zSql);
}
}
return rv;
}
JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb,
jboolean onoff){
int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0);
return rc ? JNI_TRUE : JNI_FALSE;
}
JDECL(jint,1initialize)(JENV_CSELF){
return sqlite3_initialize();
}
JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){
int rc = 0;
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
if( pStmt ){
rc = sqlite3_finalize(pStmt);
NativePointerHolder_set(env, jpStmt, 0, &S3NphRefs.sqlite3_stmt);
}
return rc;
}
JDECL(void,1interrupt)(JENV_CSELF, jobject jpDb){
sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
if( pDb ) sqlite3_interrupt(pDb);
}
JDECL(jboolean,1is_1interrupted)(JENV_CSELF, jobject jpDb){
int rc = 0;
sqlite3 * const pDb = PtrGet_sqlite3(jpDb);
if( pDb ){
rc = sqlite3_is_interrupted(pDb);
}
return rc ? JNI_TRUE : JNI_FALSE;
}
JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){
return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb));
}
/* Pre-open() code common to sqlite3_open(_v2)(). */
static int s3jni_open_pre(JNIEnv * const env, S3JniEnv **jc,
jstring jDbName, char **zDbName,
S3JniDb ** ps){
int rc = 0;
jobject jDb = 0;
*jc = S3JniGlobal_env_cache(env);
if(!*jc){
rc = SQLITE_NOMEM;
goto end;
}
*zDbName = jDbName ? s3jni_jstring_to_utf8(env, jDbName, 0) : 0;
if(jDbName && !*zDbName){
rc = SQLITE_NOMEM;
goto end;
}
jDb = new_sqlite3_wrapper(env, 0);
if( !jDb ){
sqlite3_free(*zDbName);
*zDbName = 0;
rc = SQLITE_NOMEM;
goto end;
}
*ps = S3JniDb_alloc(env, 0, jDb);
if(*ps){
(*jc)->pdbOpening = *ps;
}else{
UNREF_L(jDb);
rc = SQLITE_NOMEM;
}
end:
return rc;
}
/*
** Post-open() code common to both the sqlite3_open() and
** sqlite3_open_v2() bindings. ps->jDb must be the
** org.sqlite.jni.sqlite3 object which will hold the db's native
** pointer. theRc must be the result code of the open() op. If
** *ppDb is NULL then ps is set aside and its state cleared,
** else ps is associated with *ppDb. If *ppDb is not NULL then
** ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance).
**
** Returns theRc.
*/
static int s3jni_open_post(JNIEnv * const env, S3JniEnv * const jc,
S3JniDb * ps, sqlite3 **ppDb,
jobject jOut, int theRc){
jc->pdbOpening = 0;
if(*ppDb){
assert(ps->jDb);
if( 0==ps->pDb ){
ps->pDb = *ppDb;
NativePointerHolder_set(env, ps->jDb, *ppDb, &S3NphRefs.sqlite3);
}else{
assert( ps->pDb == *ppDb /* set up via s3jni_run_java_auto_extensions() */);
}
}else{
S3JniDb_set_aside(env, ps);
ps = 0;
}
OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0);
return theRc;
}
JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){
sqlite3 * pOut = 0;
char *zName = 0;
S3JniDb * ps = 0;
S3JniEnv * jc = 0;
int rc;
rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
if( 0==rc ){
rc = sqlite3_open(zName, &pOut);
rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
assert(rc==0 ? pOut!=0 : 1);
sqlite3_free(zName);
}
return (jint)rc;
}
JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName,
jobject jOut, jint flags, jstring strVfs){
sqlite3 * pOut = 0;
char *zName = 0;
S3JniDb * ps = 0;
S3JniEnv * jc = 0;
char *zVfs = 0;
int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps);
if( 0==rc && strVfs ){
zVfs = s3jni_jstring_to_utf8(env, strVfs, 0);
if( !zVfs ){
rc = SQLITE_NOMEM;
}
}
if( 0==rc ){
rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs);
}
rc = s3jni_open_post(env, jc, ps, &pOut, jOut, rc);
assert(rc==0 ? pOut!=0 : 1);
sqlite3_free(zName);
sqlite3_free(zVfs);
return (jint)rc;
}
/* Proxy for the sqlite3_prepare[_v2/3]() family. */
static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self,
jobject jDb, jbyteArray baSql,
jint nMax, jint prepFlags,
jobject jOutStmt, jobject outTail){
sqlite3_stmt * pStmt = 0;
jobject jStmt = 0;
const char * zTail = 0;
jbyte * const pBuf = s3jni_jbytearray_bytes(baSql);
int rc = SQLITE_ERROR;
assert(prepVersion==1 || prepVersion==2 || prepVersion==3);
if( !pBuf ){
rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM;
goto end;
}
jStmt = new_sqlite3_stmt_wrapper(env, 0);
if( !jStmt ){
rc = SQLITE_NOMEM;
goto end;
}
switch( prepVersion ){
case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jDb), (const char *)pBuf,
(int)nMax, &pStmt, &zTail);
break;
case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jDb), (const char *)pBuf,
(int)nMax, &pStmt, &zTail);
break;
case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jDb), (const char *)pBuf,
(int)nMax, (unsigned int)prepFlags,
&pStmt, &zTail);
break;
default:
assert(0 && "Invalid prepare() version");
}
end:
s3jni_jbytearray_release(baSql,pBuf);
if( 0==rc ){
if( 0!=outTail ){
/* Noting that pBuf is deallocated now but its address is all we need for
** what follows... */
assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1);
assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1);
OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0));
}
if( pStmt ){
NativePointerHolder_set(env, jStmt, pStmt, &S3NphRefs.sqlite3_stmt);
}else{
/* Happens for comments and whitespace. */
UNREF_L(jStmt);
jStmt = 0;
}
}else{
UNREF_L(jStmt);
jStmt = 0;
}
#if 0
if( 0!=rc ){
MARKER(("prepare rc = %d\n", rc));
}
#endif
OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt);
return (jint)rc;
}
JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql,
jint nMax, jobject jOutStmt, jobject outTail){
return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0,
jOutStmt, outTail);
}
JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql,
jint nMax, jobject jOutStmt, jobject outTail){
return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0,
jOutStmt, outTail);
}
JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql,
jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){
return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax,
prepFlags, jOutStmt, outTail);
}
/*
** Impl for C-to-Java of the callbacks for both sqlite3_update_hook()
** and sqlite3_preupdate_hook(). The differences are that for
** update_hook():
**
** - pDb is NULL
** - iKey1 is the row ID
** - iKey2 is unused
*/
static void s3jni_updatepre_hook_impl(void * pState, sqlite3 *pDb, int opId,
const char *zDb, const char *zTable,
sqlite3_int64 iKey1, sqlite3_int64 iKey2){
S3JniDb * const ps = pState;
LocalJniGetEnv;
jstring jDbName;
jstring jTable;
S3JniHook * pHook;
const int isPre = 0!=pDb;
pHook = isPre ?
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
&ps->hooks.preUpdate
#else
0
#endif
: &ps->hooks.update;
assert( pHook );
jDbName = s3jni_utf8_to_jstring(env, zDb, -1);
jTable = jDbName ? s3jni_utf8_to_jstring(env, zTable, -1) : 0;
IFTHREW {
EXCEPTION_CLEAR;
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
}else{
assert( pHook->jObj );
assert( pHook->midCallback );
assert( ps->jDb );
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
if( isPre ) (*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback,
ps->jDb, (jint)opId, jDbName, jTable,
(jlong)iKey1, (jlong)iKey2);
else
#endif
(*env)->CallVoidMethod(env, pHook->jObj, pHook->midCallback,
(jint)opId, jDbName, jTable, (jlong)iKey1);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("sqlite3_(pre)update_hook() callback");
s3jni_db_exception(env, ps, 0,
"sqlite3_(pre)update_hook() callback threw");
}
}
UNREF_L(jDbName);
UNREF_L(jTable);
}
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
static void s3jni_preupdate_hook_impl(void * pState, sqlite3 *pDb, int opId,
const char *zDb, const char *zTable,
sqlite3_int64 iKey1, sqlite3_int64 iKey2){
return s3jni_updatepre_hook_impl(pState, pDb, opId, zDb, zTable,
iKey1, iKey2);
}
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb,
const char *zTable, sqlite3_int64 nRowid){
return s3jni_updatepre_hook_impl(pState, NULL, opId, zDb, zTable, nRowid, 0);
}
#ifndef SQLITE_ENABLE_PREUPDATE_HOOK
/* We need no-op impls for preupdate_{count,depth,blobwrite}() */
JDECL(int,1preupdate_1blobwrite)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
JDECL(int,1preupdate_1count)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
JDECL(int,1preupdate_1depth)(JENV_CSELF, jobject jDb){ return SQLITE_MISUSE; }
#endif /* !SQLITE_ENABLE_PREUPDATE_HOOK */
/*
** JNI wrapper for both sqlite3_update_hook() and
** sqlite3_preupdate_hook() (if isPre is true).
*/
static jobject s3jni_updatepre_hook(JNIEnv * env, int isPre, jobject jDb, jobject jHook){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
jobject pOld = 0;
jmethodID xCallback;
S3JniHook * pHook = ps ? (
isPre ?
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
&ps->hooks.preUpdate
#else
0
#endif
: &ps->hooks.update) : 0;
if(!pHook){
return 0;
}
pOld = pHook->jObj;
if( pOld && jHook && (*env)->IsSameObject(env, pOld, jHook) ){
return pOld;
}
if( !jHook ){
if( pOld ){
jobject tmp = REF_L(pOld);
UNREF_G(pOld);
pOld = tmp;
}
memset(pHook, 0, sizeof(S3JniHook));
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
if( isPre ) sqlite3_preupdate_hook(ps->pDb, 0, 0);
else
#endif
sqlite3_update_hook(ps->pDb, 0, 0);
return pOld;
}
klazz = (*env)->GetObjectClass(env, jHook);
xCallback = isPre
? (*env)->GetMethodID(env, klazz, "xPreUpdate",
"(Lorg/sqlite/jni/sqlite3;"
"I"
"Ljava/lang/String;"
"Ljava/lang/String;"
"JJ)V")
: (*env)->GetMethodID(env, klazz, "xUpdateHook",
"(ILjava/lang/String;Ljava/lang/String;J)V");
UNREF_L(klazz);
IFTHREW {
EXCEPTION_CLEAR;
s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching callback on "
"(pre)update hook object.");
}else{
pHook->midCallback = xCallback;
pHook->jObj = REF_G(jHook);
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
if( isPre ) sqlite3_preupdate_hook(ps->pDb, s3jni_preupdate_hook_impl, ps);
else
#endif
sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps);
if(pOld){
jobject tmp = REF_L(pOld);
UNREF_G(pOld);
pOld = tmp;
}
}
return pOld;
}
JDECL(jobject,1preupdate_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
return s3jni_updatepre_hook(env, 1, jDb, jHook);
#else
return NULL;
#endif /* SQLITE_ENABLE_PREUPDATE_HOOK */
}
/* Impl for sqlite3_preupdate_{new,old}(). */
static int s3jni_preupdate_newold(JNIEnv * const env, int isNew, jobject jDb,
jint iCol, jobject jOut){
#ifdef SQLITE_ENABLE_PREUPDATE_HOOK
sqlite3_value * pOut = 0;
sqlite3 * const pDb = PtrGet_sqlite3(jDb);
int rc;
int (*fOrig)(sqlite3*,int,sqlite3_value**) =
isNew ? sqlite3_preupdate_new : sqlite3_preupdate_old;
rc = fOrig(pDb, (int)iCol, &pOut);
if( 0==rc ){
jobject pWrap = new_sqlite3_value_wrapper(env, pOut);
if( pWrap ){
OutputPointer_set_sqlite3_value(env, jOut, pWrap);
UNREF_L(pWrap);
}else{
rc = SQLITE_NOMEM;
}
}
return rc;
#else
return SQLITE_MISUSE;
#endif
}
JDECL(jint,1preupdate_1new)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){
return s3jni_preupdate_newold(env, 1, jDb, iCol, jOut);
}
JDECL(jint,1preupdate_1old)(JENV_CSELF, jobject jDb, jint iCol, jobject jOut){
return s3jni_preupdate_newold(env, 0, jDb, iCol, jOut);
}
/* Central C-to-Java sqlite3_progress_handler() proxy. */
static int s3jni_progress_handler_impl(void *pP){
S3JniDb * const ps = (S3JniDb *)pP;
LocalJniGetEnv;
int rc = (int)(*env)->CallIntMethod(env, ps->hooks.progress.jObj,
ps->hooks.progress.midCallback);
IFTHREW{
rc = s3jni_db_exception(env, ps, rc,
"sqlite3_progress_handler() callback threw");
}
return rc;
}
JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){
S3JniDb * ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
jmethodID xCallback;
if( n<1 || !jProgress ){
if(ps){
UNREF_G(ps->hooks.progress.jObj);
memset(&ps->hooks.progress, 0, sizeof(ps->hooks.progress));
}
sqlite3_progress_handler(ps->pDb, 0, 0, 0);
return;
}
if(!ps){
s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0);
return;
}
klazz = (*env)->GetObjectClass(env, jProgress);
xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I");
UNREF_L(klazz);
IFTHREW {
EXCEPTION_CLEAR;
s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching xCallback() on "
"ProgressHandler object.");
}else{
UNREF_G(ps->hooks.progress.jObj);
ps->hooks.progress.midCallback = xCallback;
ps->hooks.progress.jObj = REF_G(jProgress);
sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps);
}
}
JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){
int rc = 0;
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
if( pStmt ){
rc = sqlite3_reset(pStmt);
}
return rc;
}
/* Clears all entries from S3JniGlobal.autoExt. */
static void s3jni_reset_auto_extension(JNIEnv *env){
int i;
MUTEX_EXT_ENTER;
for( i = 0; i < SJG.autoExt.nExt; ++i ){
S3JniAutoExtension_clear( env, &SJG.autoExt.pExt[i] );
}
SJG.autoExt.nExt = 0;
MUTEX_EXT_LEAVE;
}
JDECL(void,1reset_1auto_1extension)(JENV_CSELF){
s3jni_reset_auto_extension(env);
}
/* sqlite3_result_text/blob() and friends. */
static void result_blob_text(int asBlob, int as64,
int eTextRep/*only for (asBlob=0)*/,
JNIEnv * const env, sqlite3_context *pCx,
jbyteArray jBa, jlong nMax){
if(jBa){
jbyte * const pBuf = s3jni_jbytearray_bytes(jBa);
jsize nBa = (*env)->GetArrayLength(env, jBa);
if( nMax>=0 && nBa>(jsize)nMax ){
nBa = (jsize)nMax;
/**
From the sqlite docs:
> If the 3rd parameter to any of the sqlite3_result_text*
interfaces other than sqlite3_result_text64() is negative,
then SQLite computes the string length itself by searching
the 2nd parameter for the first zero character.
Note that the text64() interfaces take an unsigned value for
the length, which Java does not support. This binding takes
the approach of passing on negative values to the C API,
which will, in turn fail with SQLITE_TOOBIG at some later
point (recall that the sqlite3_result_xyz() family do not
have result values).
*/
}
if(as64){ /* 64-bit... */
static const jsize nLimit64 =
SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/
/* jsize is int32, not int64! */;
if(nBa > nLimit64){
sqlite3_result_error_toobig(pCx);
}else if(asBlob){
sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa,
SQLITE_TRANSIENT);
}else{ /* text64... */
if(encodingTypeIsValid(eTextRep)){
sqlite3_result_text64(pCx, (const char *)pBuf,
(sqlite3_uint64)nBa,
SQLITE_TRANSIENT, eTextRep);
}else{
sqlite3_result_error_code(pCx, SQLITE_FORMAT);
}
}
}else{ /* 32-bit... */
static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE;
if(nBa > nLimit){
sqlite3_result_error_toobig(pCx);
}else if(asBlob){
sqlite3_result_blob(pCx, pBuf, (int)nBa,
SQLITE_TRANSIENT);
}else{
switch(eTextRep){
case SQLITE_UTF8:
sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa,
SQLITE_TRANSIENT);
break;
case SQLITE_UTF16:
sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBa,
SQLITE_TRANSIENT);
break;
case SQLITE_UTF16LE:
sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBa,
SQLITE_TRANSIENT);
break;
case SQLITE_UTF16BE:
sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBa,
SQLITE_TRANSIENT);
break;
}
}
s3jni_jbytearray_release(jBa, pBuf);
}
}else{
sqlite3_result_null(pCx);
}
}
JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){
return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
}
JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){
return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax);
}
JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){
sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v);
}
JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg,
int eTextRep){
const char * zUnspecified = "Unspecified error.";
jsize const baLen = (*env)->GetArrayLength(env, baMsg);
jbyte * const pjBuf = baMsg ? s3jni_jbytearray_bytes(baMsg) : NULL;
switch(pjBuf ? eTextRep : SQLITE_UTF8){
case SQLITE_UTF8: {
const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified;
sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
break;
}
case SQLITE_UTF16: {
const void *zMsg = pjBuf
? (const void *)pjBuf : (const void *)zUnspecified;
sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen);
break;
}
default:
sqlite3_result_error(PtrGet_sqlite3_context(jpCx),
"Invalid encoding argument passed "
"to sqlite3_result_error().", -1);
break;
}
s3jni_jbytearray_release(baMsg,pjBuf);
}
JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){
sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR);
}
JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){
sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
}
JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){
sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx));
}
JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){
sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v);
}
JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){
sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v);
}
JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){
if(v){
ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v);
if(rjv){
sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv,
ResultJavaValuePtrStr, ResultJavaVal_finalizer);
}else{
sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
}
}else{
sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
}
}
JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){
sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
}
JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){
return result_blob_text(0, 0, SQLITE_UTF8, env,
PtrGet_sqlite3_context(jpCx), jBa, nMax);
}
JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax,
jint eTextRep){
return result_blob_text(0, 1, eTextRep, env,
PtrGet_sqlite3_context(jpCx), jBa, nMax);
}
JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){
sqlite3_result_value(PtrGet_sqlite3_context(jpCx),
PtrGet_sqlite3_value(jpSVal));
}
JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){
sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v);
}
JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){
return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx),
(sqlite3_int64)v);
}
JDECL(jobject,1rollback_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
return s3jni_commit_rollback_hook(0, env, jDb, jHook);
}
/* sqlite3_set_authorizer() callback proxy. */
static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1,
const char*z2,const char*z3){
S3JniDb * const ps = pState;
LocalJniGetEnv;
S3JniHook const * const pHook = &ps->hooks.auth;
jstring const s0 = z0 ? s3jni_utf8_to_jstring(env, z0, -1) : 0;
jstring const s1 = z1 ? s3jni_utf8_to_jstring(env, z1, -1) : 0;
jstring const s2 = z2 ? s3jni_utf8_to_jstring(env, z2, -1) : 0;
jstring const s3 = z3 ? s3jni_utf8_to_jstring(env, z3, -1) : 0;
int rc;
assert( pHook->jObj );
rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op,
s0, s1, s3, s3);
IFTHREW{
rc = s3jni_db_exception(env, ps, rc, "sqlite3_set_authorizer() callback");
}
UNREF_L(s0);
UNREF_L(s1);
UNREF_L(s2);
UNREF_L(s3);
return rc;
}
JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
S3JniHook * const pHook = ps ? &ps->hooks.auth : 0;
if( !ps ) return SQLITE_MISUSE;
else if( !jHook ){
S3JniHook_unref(env, pHook, 0);
return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 );
}else{
int rc = 0;
jclass klazz;
if( pHook->jObj ){
if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){
/* Same object - this is a no-op. */
return 0;
}
S3JniHook_unref(env, pHook, 0);
}
pHook->jObj = REF_G(jHook);
klazz = (*env)->GetObjectClass(env, jHook);
pHook->midCallback = (*env)->GetMethodID(env, klazz,
"xAuth",
"(I"
"Ljava/lang/String;"
"Ljava/lang/String;"
"Ljava/lang/String;"
"Ljava/lang/String;"
")I");
UNREF_L(klazz);
IFTHREW {
S3JniHook_unref(env, pHook, 0);
return s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Error setting up Java parts of authorizer hook.");
}
rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps);
if( rc ) S3JniHook_unref(env, pHook, 0);
return rc;
}
}
JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){
sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb),
(sqlite3_int64)rowId);
}
JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh,
jboolean reset ){
int iCur = 0, iHigh = 0;
int rc = sqlite3_status( op, &iCur, &iHigh, reset );
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCurrent, iCur);
OutputPointer_set_Int32(env, jOutHigh, iHigh);
}
return (jint)rc;
}
JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh,
jboolean reset ){
sqlite3_int64 iCur = 0, iHigh = 0;
int rc = sqlite3_status64( op, &iCur, &iHigh, reset );
if( 0==rc ){
OutputPointer_set_Int64(env, jOutCurrent, iCur);
OutputPointer_set_Int64(env, jOutHigh, iHigh);
}
return (jint)rc;
}
static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
jbyteArray baG, jbyteArray baT, jint escLike){
int rc = 0;
jbyte * const pG = s3jni_jbytearray_bytes(baG);
jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0;
s3jni_oom_check(pT);
/* Note that we're relying on the byte arrays having been
NUL-terminated on the Java side. */
rc = isLike
? sqlite3_strlike((const char *)pG, (const char *)pT,
(unsigned int)escLike)
: sqlite3_strglob((const char *)pG, (const char *)pT);
s3jni_jbytearray_release(baG, pG);
s3jni_jbytearray_release(baT, pT);
return rc;
}
JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){
return s3jni_strlike_glob(0, env, baG, baT, 0);
}
JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){
return s3jni_strlike_glob(1, env, baG, baT, escChar);
}
JDECL(jint,1shutdown)(JENV_CSELF){
s3jni_reset_auto_extension(env);
MUTEX_ENV_ENTER;
while( SJG.envCache.aHead ){
S3JniGlobal_env_uncache( SJG.envCache.aHead->env );
}
MUTEX_ENV_LEAVE;
/* Do not clear S3JniGlobal.jvm: it's legal to call
sqlite3_initialize() again to restart the lib. */
return sqlite3_shutdown();
}
JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt);
jstring rv = 0;
if( pStmt ){
const char * zSql = 0;
zSql = sqlite3_sql(pStmt);
rv = s3jni_utf8_to_jstring(env, zSql, -1);
s3jni_oom_check(rv);
}
return rv;
}
JDECL(jint,1step)(JENV_CSELF,jobject jStmt){
int rc = SQLITE_MISUSE;
sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt);
if(pStmt){
rc = sqlite3_step(pStmt);
}
return rc;
}
static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){
S3JniDb * const ps = (S3JniDb *)pC;
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 */;
int rc;
int createStmt = 0;
switch(traceflag){
case SQLITE_TRACE_STMT:
jX = s3jni_utf8_to_jstring(env, (const char *)pX, -1);
if(!jX) return SQLITE_NOMEM;
/*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/
createStmt = 1;
break;
case SQLITE_TRACE_PROFILE:
jX = (*env)->NewObject(env, SJG.g.cLong, SJG.g.ctorLong1,
(jlong)*((sqlite3_int64*)pX));
// hmm. ^^^ (*pX) really is zero.
// MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX)));
if(!jX) return SQLITE_NOMEM;
createStmt = 1;
break;
case SQLITE_TRACE_ROW:
createStmt = 1;
break;
case SQLITE_TRACE_CLOSE:
jP = ps->jDb;
break;
default:
assert(!"cannot happen - unkown trace flag");
return SQLITE_ERROR;
}
if( createStmt ){
jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP);
if(!jP){
UNREF_L(jX);
return SQLITE_NOMEM;
}
}
assert(jP);
rc = (int)(*env)->CallIntMethod(env, ps->hooks.trace.jObj,
ps->hooks.trace.midCallback,
(jint)traceflag, jP, jX);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback");
rc = s3jni_db_exception(env, ps, SQLITE_ERROR,
"sqlite3_trace_v2() callback threw.");
}
UNREF_L(jPUnref);
UNREF_L(jX);
return rc;
}
JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jclass klazz;
if( !traceMask || !jTracer ){
if(ps){
S3JniHook_unref(env, &ps->hooks.trace, 0);
}
return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0);
}
if(!ps) return SQLITE_NOMEM;
klazz = (*env)->GetObjectClass(env, jTracer);
ps->hooks.trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
"(ILjava/lang/Object;Ljava/lang/Object;)I");
UNREF_L(klazz);
IFTHREW {
EXCEPTION_CLEAR;
return s3jni_db_error(ps->pDb, SQLITE_ERROR,
"Cannot not find matching xCallback() on Tracer object.");
}
ps->hooks.trace.jObj = REF_G(jTracer);
return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps);
}
JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){
return s3jni_updatepre_hook(env, 0, jDb, jHook);
}
JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){
sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
int const nLen = sqlite3_value_bytes(sv);
const jbyte * pBytes = sqlite3_value_blob(sv);
jbyteArray const jba = pBytes
? (*env)->NewByteArray(env, (jsize)nLen)
: NULL;
if(jba){
(*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes);
}
return jba;
}
JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){
return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal));
}
JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){
sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal));
return sv ? new_sqlite3_value_wrapper(env, sv) : 0;
}
JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){
sqlite3_value_free(PtrGet_sqlite3_value(jpSVal));
}
JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){
return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal));
}
JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){
return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal));
}
JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){
ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal),
ResultJavaValuePtrStr);
return rv ? rv->jObj : NULL;
}
JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){
sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
int const n = sqlite3_value_bytes(sv);
const unsigned char * const p = sqlite3_value_text(sv);
return p ? s3jni_new_jbyteArray(env, p, n) : 0;
}
static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){
sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
int const nLen = sqlite3_value_bytes16(sv);
jbyteArray jba;
const jbyte * pBytes;
switch(mode){
case SQLITE_UTF16:
pBytes = sqlite3_value_text16(sv);
break;
case SQLITE_UTF16LE:
pBytes = sqlite3_value_text16le(sv);
break;
case SQLITE_UTF16BE:
pBytes = sqlite3_value_text16be(sv);
break;
default:
assert(!"not possible");
return NULL;
}
jba = pBytes
? (*env)->NewByteArray(env, (jsize)nLen)
: NULL;
if(jba){
(*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes);
}
return jba;
}
JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){
return value_text16(SQLITE_UTF16, env, jpSVal);
}
JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){
return value_text16(SQLITE_UTF16LE, env, jpSVal);
}
JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){
return value_text16(SQLITE_UTF16BE, env, jpSVal);
}
JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){
MARKER(("\nVarious bits of internal info:\n"));
puts("FTS5 is "
#ifdef SQLITE_ENABLE_FTS5
"available"
#else
"unavailable"
#endif
"."
);
puts("sizeofs:");
#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T))
SO(void*);
SO(jmethodID);
SO(jfieldID);
SO(S3JniEnv);
SO(S3JniHook);
SO(S3JniDb);
SO(S3NphRefs);
printf("\t(^^^ %u NativePointerHolder subclasses)\n",
(unsigned)NphCache_SIZE);
SO(S3JniGlobal);
SO(S3JniAutoExtension);
SO(S3JniUdf);
printf("Cache info:\n");
printf("\tJNIEnv cache: %u allocs, %u misses, %u hits\n",
SJG.metrics.envCacheAllocs,
SJG.metrics.envCacheMisses,
SJG.metrics.envCacheHits);
printf("Mutex entry:"
"\n\tenv %u"
"\n\tnph inits %u"
"\n\tperDb %u"
"\n\tautoExt %u list accesses"
"\n\tmetrics %u\n",
SJG.metrics.nMutexEnv, SJG.metrics.nMutexEnv2,
SJG.metrics.nMutexPerDb, SJG.metrics.nMutexAutoExt,
SJG.metrics.nMetrics);
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);
#undef UDF
printf("xDestroy calls across all callback types: %u\n",
SJG.metrics.nDestroy);
#undef SO
}
////////////////////////////////////////////////////////////////////////
// End of the sqlite3_... API bindings. Next up, FTS5...
////////////////////////////////////////////////////////////////////////
#ifdef SQLITE_ENABLE_FTS5
/* Creates a verbose JNI Fts5 function name. */
#define JFuncNameFtsXA(Suffix) \
Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix
#define JFuncNameFtsApi(Suffix) \
Java_org_sqlite_jni_fts5_1api_ ## Suffix
#define JFuncNameFtsTok(Suffix) \
Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix
#define JDECLFtsXA(ReturnType,Suffix) \
JNIEXPORT ReturnType JNICALL \
JFuncNameFtsXA(Suffix)
#define JDECLFtsApi(ReturnType,Suffix) \
JNIEXPORT ReturnType JNICALL \
JFuncNameFtsApi(Suffix)
#define JDECLFtsTok(ReturnType,Suffix) \
JNIEXPORT ReturnType JNICALL \
JFuncNameFtsTok(Suffix)
#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.fts5_api)
#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.fts5_tokenizer)
#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.Fts5Context)
#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,&S3NphRefs.Fts5Tokenizer)
#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext()
/**
State for binding Java-side FTS5 auxiliary functions.
*/
typedef struct {
jobject jObj /* functor instance */;
jobject jUserData /* 2nd arg to JNI binding of
xCreateFunction(), ostensibly the 3rd arg
to the lib-level xCreateFunction(), except
that we necessarily use that slot for a
Fts5JniAux instance. */;
char * zFuncName /* Only for error reporting and debug logging */;
jmethodID jmid /* callback member's method ID */;
} Fts5JniAux;
static void Fts5JniAux_free(Fts5JniAux * const s){
LocalJniGetEnv;
if(env){
/*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/
s3jni_call_xDestroy(env, s->jObj);
UNREF_G(s->jObj);
UNREF_G(s->jUserData);
}
sqlite3_free(s->zFuncName);
sqlite3_free(s);
}
static void Fts5JniAux_xDestroy(void *p){
if(p) Fts5JniAux_free(p);
}
static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){
Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux));
if(s){
jclass klazz;
memset(s, 0, sizeof(Fts5JniAux));
s->jObj = REF_G(jObj);
klazz = (*env)->GetObjectClass(env, jObj);
s->jmid = (*env)->GetMethodID(env, klazz, "xFunction",
"(Lorg/sqlite/jni/Fts5ExtensionApi;"
"Lorg/sqlite/jni/Fts5Context;"
"Lorg/sqlite/jni/sqlite3_context;"
"[Lorg/sqlite/jni/sqlite3_value;)V");
UNREF_L(klazz);
IFTHREW{
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
Fts5JniAux_free(s);
s = 0;
}
}
return s;
}
static inline Fts5ExtensionApi const * s3jni_ftsext(void){
return &sFts5Api/*singleton from sqlite3.c*/;
}
static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){
return new_NativePointerHolder_object(env, &S3NphRefs.Fts5Context, sv);
}
static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){
return new_NativePointerHolder_object(env, &S3NphRefs.fts5_api, sv);
}
/**
Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton
instance, or NULL on OOM.
*/
static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){
if( !SJG.fts5.jFtsExt ){
jobject pNPH = new_NativePointerHolder_object(
env, &S3NphRefs.Fts5ExtensionApi, s3jni_ftsext()
);
MUTEX_ENV_ENTER;
if( pNPH ){
if( !SJG.fts5.jFtsExt ){
SJG.fts5.jFtsExt = REF_G(pNPH);
}
UNREF_L(pNPH);
}
MUTEX_ENV_LEAVE;
}
return SJG.fts5.jFtsExt;
}
/*
** Return a pointer to the fts5_api instance for database connection
** db. If an error occurs, return NULL and leave an error in the
** database handle (accessible using sqlite3_errcode()/errmsg()).
*/
static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){
fts5_api *pRet = 0;
sqlite3_stmt *pStmt = 0;
if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){
sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL);
sqlite3_step(pStmt);
}
sqlite3_finalize(pStmt);
return pRet;
}
JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){
S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0);
jobject rv = 0;
if(!ps) return 0;
else if(ps->jFtsApi){
rv = ps->jFtsApi;
}else{
fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb);
if( pApi ){
rv = new_fts5_api_wrapper(env, pApi);
ps->jFtsApi = rv ? REF_G(rv) : 0;
}
}
return rv;
}
JDECLFtsXA(jobject,getInstance)(JENV_CSELF){
return s3jni_getFts5ExensionApi(env);
}
JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){
Fts5ExtDecl;
return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx));
}
JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){
Fts5ExtDecl;
int n1 = 0;
int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1);
if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1);
return rc;
}
JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol,
jobject jOut){
Fts5ExtDecl;
const char *pz = 0;
int pn = 0;
int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol,
&pz, &pn);
if( 0==rc ){
jstring jstr = pz ? s3jni_utf8_to_jstring(env, pz, pn) : 0;
if( pz ){
if( jstr ){
OutputPointer_set_String(env, jOut, jstr);
UNREF_L(jstr)/*jOut has a reference*/;
}else{
rc = SQLITE_NOMEM;
}
}
}
return (jint)rc;
}
JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){
Fts5ExtDecl;
sqlite3_int64 nOut = 0;
int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut);
if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
return (jint)rc;
}
/*
** Proxy for fts5_extension_function instances plugged in via
** fts5_api::xCreateFunction().
*/
static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi,
Fts5Context *pFts,
sqlite3_context *pCx,
int argc,
sqlite3_value **argv){
Fts5JniAux * const pAux = pApi->xUserData(pFts);
jobject jpCx = 0;
jobjectArray jArgv = 0;
jobject jpFts = 0;
jobject jFXA;
int rc;
LocalJniGetEnv;
assert(pAux);
jFXA = s3jni_getFts5ExensionApi(env);
if( !jFXA ) goto error_oom;
jpFts = new_Fts5Context_wrapper(env, pFts);
if(!jpFts) goto error_oom;
rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv);
if(rc) goto error_oom;
(*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid,
jFXA, jpFts, jpCx, jArgv);
IFTHREW{
udf_report_exception(env, 1, pCx, pAux->zFuncName, "xFunction");
}
UNREF_L(jpFts);
UNREF_L(jpCx);
UNREF_L(jArgv);
return;
error_oom:
assert( !jArgv );
assert( !jpCx );
UNREF_L(jpFts);
sqlite3_result_error_nomem(pCx);
return;
}
JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName,
jobject jUserData, jobject jFunc){
fts5_api * const pApi = PtrGet_fts5_api(jSelf);
int rc;
char const * zName;
Fts5JniAux * pAux;
assert(pApi);
zName = s3jni_jstring_to_mutf8(jName);
if(!zName) return SQLITE_NOMEM;
pAux = Fts5JniAux_alloc(env, jFunc);
if( pAux ){
rc = pApi->xCreateFunction(pApi, zName, pAux,
s3jni_fts5_extension_function,
Fts5JniAux_xDestroy);
}else{
rc = SQLITE_NOMEM;
}
if( 0==rc ){
pAux->jUserData = jUserData ? REF_G(jUserData) : 0;
pAux->zFuncName = sqlite3_mprintf("%s", zName)
/* OOM here is non-fatal. Ignore it. */;
}
s3jni_mutf8_release(jName, zName);
return (jint)rc;
}
typedef struct S3JniFts5AuxData S3JniFts5AuxData;
/*
** TODO: this middle-man struct is no longer necessary. Conider
** removing it and passing around jObj itself instead.
*/
struct S3JniFts5AuxData {
jobject jObj;
};
static void S3JniFts5AuxData_xDestroy(void *x){
if(x){
S3JniFts5AuxData * const p = x;
if(p->jObj){
LocalJniGetEnv;
s3jni_call_xDestroy(env, p->jObj);
UNREF_G(p->jObj);
}
sqlite3_free(x);
}
}
JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){
Fts5ExtDecl;
jobject rv = 0;
S3JniFts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear);
if(pAux){
if(bClear){
if( pAux->jObj ){
rv = REF_L(pAux->jObj);
UNREF_G(pAux->jObj);
}
/* Note that we do not call xDestroy() in this case. */
sqlite3_free(pAux);
}else{
rv = pAux->jObj;
}
}
return rv;
}
JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase,
jobject jOutCol, jobject jOutOff){
Fts5ExtDecl;
int n1 = 0, n2 = 2, n3 = 0;
int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutPhrase, n1);
OutputPointer_set_Int32(env, jOutCol, n2);
OutputPointer_set_Int32(env, jOutOff, n3);
}
return rc;
}
JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){
Fts5ExtDecl;
int nOut = 0;
int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut);
if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut);
return (jint)rc;
}
JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){
Fts5ExtDecl;
return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx));
}
/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */
static void s3jni_phraseIter_NToJ(JNIEnv *const env,
Fts5PhraseIter const * const pSrc,
jobject jIter){
S3JniGlobalType * const g = &S3JniGlobal;
assert(g->fts5.jPhraseIter.fidA);
(*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidA, (jlong)pSrc->a);
EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field.");
(*env)->SetLongField(env, jIter, g->fts5.jPhraseIter.fidB, (jlong)pSrc->b);
EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field.");
}
/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */
static void s3jni_phraseIter_JToN(JNIEnv *const env, jobject jIter,
Fts5PhraseIter * const pDest){
S3JniGlobalType * const g = &S3JniGlobal;
assert(g->fts5.jPhraseIter.fidA);
pDest->a =
(const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidA);
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
pDest->b =
(const unsigned char *)(*env)->GetLongField(env, jIter, g->fts5.jPhraseIter.fidB);
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
}
JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase,
jobject jIter, jobject jOutCol,
jobject jOutOff){
Fts5ExtDecl;
Fts5PhraseIter iter;
int rc, iCol = 0, iOff = 0;
rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase,
&iter, &iCol, &iOff);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCol, iCol);
OutputPointer_set_Int32(env, jOutOff, iOff);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
return rc;
}
JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase,
jobject jIter, jobject jOutCol){
Fts5ExtDecl;
Fts5PhraseIter iter;
int rc, iCol = 0;
rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase,
&iter, &iCol);
if( 0==rc ){
OutputPointer_set_Int32(env, jOutCol, iCol);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
return rc;
}
JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter,
jobject jOutCol, jobject jOutOff){
Fts5ExtDecl;
Fts5PhraseIter iter;
int iCol = 0, iOff = 0;
s3jni_phraseIter_JToN(env, jIter, &iter);
fext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff);
OutputPointer_set_Int32(env, jOutCol, iCol);
OutputPointer_set_Int32(env, jOutOff, iOff);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter,
jobject jOutCol){
Fts5ExtDecl;
Fts5PhraseIter iter;
int iCol = 0;
s3jni_phraseIter_JToN(env, jIter, &iter);
fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol);
OutputPointer_set_Int32(env, jOutCol, iCol);
s3jni_phraseIter_NToJ(env, &iter, jIter);
}
JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){
Fts5ExtDecl;
return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase);
}
/* State for use with xQueryPhrase() and xTokenize(). */
struct s3jni_xQueryPhraseState {
Fts5ExtensionApi const * fext;
S3JniEnv const * jc;
jmethodID midCallback;
jobject jCallback;
jobject jFcx;
/* State for xTokenize() */
struct {
const char * zPrev;
int nPrev;
jbyteArray jba;
} tok;
};
static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi,
Fts5Context * pFcx, void *pData){
/* TODO: confirm that the Fts5Context passed to this function is
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;
LocalJniGetEnv;
int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
SJG.fts5.jFtsExt, s->jFcx);
IFTHREW{
EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase() callback");
EXCEPTION_CLEAR;
rc = SQLITE_ERROR;
}
return rc;
}
JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase,
jobject jCallback){
Fts5ExtDecl;
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
struct s3jni_xQueryPhraseState s;
jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
if( !klazz ) return SQLITE_MISUSE;
s.jc = jc;
s.jCallback = jCallback;
s.jFcx = jFcx;
s.fext = fext;
s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback",
"(Lorg.sqlite.jni.Fts5ExtensionApi;"
"Lorg.sqlite.jni.Fts5Context;)I");
UNREF_L(klazz);
EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method.");
return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s,
s3jni_xQueryPhrase);
}
JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){
Fts5ExtDecl;
sqlite3_int64 nOut = 0;
int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut);
if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut);
return (jint)rc;
}
JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){
Fts5ExtDecl;
return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx));
}
JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){
Fts5ExtDecl;
int rc;
S3JniFts5AuxData * pAux;
pAux = sqlite3_malloc(sizeof(*pAux));
if(!pAux){
if(jAux){
/* Emulate how xSetAuxdata() behaves when it cannot alloc
** its auxdata wrapper. */
s3jni_call_xDestroy(env, jAux);
}
return SQLITE_NOMEM;
}
pAux->jObj = REF_G(jAux);
rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux,
S3JniFts5AuxData_xDestroy);
return rc;
}
/* xToken() impl for xTokenize(). */
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;
jbyteArray jba;
if( s->tok.zPrev == z && s->tok.nPrev == nZ ){
jba = s->tok.jba;
}else{
if(s->tok.jba){
UNREF_L(s->tok.jba);
}
s->tok.zPrev = z;
s->tok.nPrev = nZ;
s->tok.jba = (*env)->NewByteArray(env, (jint)nZ);
if( !s->tok.jba ) return SQLITE_NOMEM;
jba = s->tok.jba;
(*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z);
}
rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback,
(jint)tFlags, jba, (jint)iStart,
(jint)iEnd);
return rc;
}
/*
** Proxy for Fts5ExtensionApi.xTokenize() and
** fts5_tokenizer.xTokenize()
*/
static jint s3jni_fts5_xTokenize(JENV_OSELF, S3NphRef const *pRef,
jint tokFlags, jobject jFcx,
jbyteArray jbaText, jobject jCallback){
Fts5ExtDecl;
S3JniEnv * const jc = S3JniGlobal_env_cache(env);
struct s3jni_xQueryPhraseState s;
int rc = 0;
jbyte * const pText = jCallback ? s3jni_jbytearray_bytes(jbaText) : 0;
jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0;
jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL;
if( !klazz ) return SQLITE_MISUSE;
memset(&s, 0, sizeof(s));
s.jc = jc;
s.jCallback = jCallback;
s.jFcx = jFcx;
s.fext = fext;
s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I");
UNREF_L(klazz);
IFTHREW {
EXCEPTION_REPORT;
EXCEPTION_CLEAR;
s3jni_jbytearray_release(jbaText, pText);
return SQLITE_ERROR;
}
s.tok.jba = REF_L(jbaText);
s.tok.zPrev = (const char *)pText;
s.tok.nPrev = (int)nText;
if( pRef == &S3NphRefs.Fts5ExtensionApi ){
rc = fext->xTokenize(PtrGet_Fts5Context(jFcx),
(const char *)pText, (int)nText,
&s, s3jni_xTokenize_xToken);
}else if( pRef == &S3NphRefs.fts5_tokenizer ){
fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf);
rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags,
(const char *)pText, (int)nText,
s3jni_xTokenize_xToken);
}else{
(*env)->FatalError(env, "This cannot happen. Maintenance required.");
}
if(s.tok.jba){
assert( s.tok.zPrev );
UNREF_L(s.tok.jba);
}
s3jni_jbytearray_release(jbaText, pText);
return (jint)rc;
}
JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText,
jobject jCallback){
return s3jni_fts5_xTokenize(env, jSelf, &S3NphRefs.Fts5ExtensionApi,
0, jFcx, jbaText, jCallback);
}
JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags,
jbyteArray jbaText, jobject jCallback){
return s3jni_fts5_xTokenize(env, jSelf, &S3NphRefs.Fts5Tokenizer,
tokFlags, jFcx, jbaText, jCallback);
}
JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){
Fts5ExtDecl;
Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx));
return pAux ? pAux->jUserData : 0;
}
#endif /* SQLITE_ENABLE_FTS5 */
////////////////////////////////////////////////////////////////////////
// End of the main API bindings. Start of SQLTester bits...
////////////////////////////////////////////////////////////////////////
#ifdef S3JNI_ENABLE_SQLTester
typedef struct SQLTesterJni SQLTesterJni;
struct SQLTesterJni {
sqlite3_int64 nDup;
};
static SQLTesterJni SQLTester = {
0
};
static void SQLTester_dup_destructor(void*pToFree){
u64 *p = (u64*)pToFree;
assert( p!=0 );
p--;
assert( p[0]==0x2bbf4b7c );
p[0] = 0;
p[1] = 0;
sqlite3_free(p);
}
/*
** Implementation of
**
** dup(TEXT)
**
** This SQL function simply makes a copy of its text argument. But it
** returns the result using a custom destructor, in order to provide
** tests for the use of Mem.xDel() in the SQLite VDBE.
*/
static void SQLTester_dup_func(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
u64 *pOut;
char *z;
int n = sqlite3_value_bytes(argv[0]);
SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
++p->nDup;
if( n>0 && (pOut = sqlite3_malloc( (n+16)&~7 ))!=0 ){
pOut[0] = 0x2bbf4b7c;
z = (char*)&pOut[1];
memcpy(z, sqlite3_value_text(argv[0]), n);
z[n] = 0;
sqlite3_result_text(context, z, n, SQLTester_dup_destructor);
}
return;
}
/*
** Return the number of calls to the dup() SQL function since the
** SQLTester context was opened or since the last dup_count() call.
*/
static void SQLTester_dup_count_func(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context);
sqlite3_result_int64(context, p->nDup);
p->nDup = 0;
}
/*
** Return non-zero if string z matches glob pattern zGlob and zero if the
** pattern does not match.
**
** To repeat:
**
** zero == no match
** non-zero == match
**
** Globbing rules:
**
** '*' Matches any sequence of zero or more characters.
**
** '?' Matches exactly one character.
**
** [...] Matches one character from the enclosed list of
** characters.
**
** [^...] Matches one character not in the enclosed list.
**
** '#' Matches any sequence of one or more digits with an
** optional + or - sign in front, or a hexadecimal
** literal of the form 0x...
*/
static int SQLTester_strnotglob(const char *zGlob, const char *z){
int c, c2;
int invert;
int seen;
while( (c = (*(zGlob++)))!=0 ){
if( c=='*' ){
while( (c=(*(zGlob++))) == '*' || c=='?' ){
if( c=='?' && (*(z++))==0 ) return 0;
}
if( c==0 ){
return 1;
}else if( c=='[' ){
while( *z && SQLTester_strnotglob(zGlob-1,z)==0 ){
z++;
}
return (*z)!=0;
}
while( (c2 = (*(z++)))!=0 ){
while( c2!=c ){
c2 = *(z++);
if( c2==0 ) return 0;
}
if( SQLTester_strnotglob(zGlob,z) ) return 1;
}
return 0;
}else if( c=='?' ){
if( (*(z++))==0 ) return 0;
}else if( c=='[' ){
int prior_c = 0;
seen = 0;
invert = 0;
c = *(z++);
if( c==0 ) return 0;
c2 = *(zGlob++);
if( c2=='^' ){
invert = 1;
c2 = *(zGlob++);
}
if( c2==']' ){
if( c==']' ) seen = 1;
c2 = *(zGlob++);
}
while( c2 && c2!=']' ){
if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
c2 = *(zGlob++);
if( c>=prior_c && c<=c2 ) seen = 1;
prior_c = 0;
}else{
if( c==c2 ){
seen = 1;
}
prior_c = c2;
}
c2 = *(zGlob++);
}
if( c2==0 || (seen ^ invert)==0 ) return 0;
}else if( c=='#' ){
if( z[0]=='0'
&& (z[1]=='x' || z[1]=='X')
&& sqlite3Isxdigit(z[2])
){
z += 3;
while( sqlite3Isxdigit(z[0]) ){ z++; }
}else{
if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++;
if( !sqlite3Isdigit(z[0]) ) return 0;
z++;
while( sqlite3Isdigit(z[0]) ){ z++; }
}
}else{
if( c!=(*(z++)) ) return 0;
}
}
return *z==0;
}
JNIEXPORT jint JNICALL
Java_org_sqlite_jni_tester_SQLTester_strglob(
JENV_CSELF, jbyteArray baG, jbyteArray baT
){
int rc = 0;
jbyte * const pG = s3jni_jbytearray_bytes(baG);
jbyte * const pT = pG ? s3jni_jbytearray_bytes(baT) : 0;
s3jni_oom_check(pT);
/* Note that we're relying on the byte arrays having been
NUL-terminated on the Java side. */
rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT);
s3jni_jbytearray_release(baG, pG);
s3jni_jbytearray_release(baT, pT);
return rc;
}
static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr,
const struct sqlite3_api_routines *ignored){
sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester,
SQLTester_dup_func, 0, 0);
sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester,
SQLTester_dup_count_func, 0, 0);
return 0;
}
JNIEXPORT void JNICALL
Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){
sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension );
}
#endif /* S3JNI_ENABLE_SQLTester */
////////////////////////////////////////////////////////////////////////
// End of SQLTester bindings. Start of lower-level bits.
////////////////////////////////////////////////////////////////////////
/*
** Uncaches the current JNIEnv from the S3JniGlobal state, clearing any
** resources owned by that cache entry and making that slot available
** for re-use. It is important that the Java-side decl of this
** function be declared as synchronous.
*/
JNIEXPORT jboolean JNICALL
Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){
int rc;
MUTEX_ENV_ENTER;
rc = S3JniGlobal_env_uncache(env);
MUTEX_ENV_LEAVE;
return rc ? JNI_TRUE : JNI_FALSE;
}
/*
** Called during static init of the SQLite3Jni class to sync certain
** compile-time constants to Java-space.
**
** This routine is part of the reason why we have to #include
** sqlite3.c instead of sqlite3.h.
*/
JNIEXPORT void JNICALL
Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){
enum JType {
JTYPE_INT,
JTYPE_BOOL
};
typedef struct {
const char *zName;
enum JType jtype;
int value;
} ConfigFlagEntry;
const ConfigFlagEntry aLimits[] = {
{"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE},
{"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH},
{"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH},
{"SQLITE_LIMIT_SQL_LENGTH", JTYPE_INT, SQLITE_LIMIT_SQL_LENGTH},
{"SQLITE_MAX_SQL_LENGTH", JTYPE_INT, SQLITE_MAX_SQL_LENGTH},
{"SQLITE_LIMIT_COLUMN", JTYPE_INT, SQLITE_LIMIT_COLUMN},
{"SQLITE_MAX_COLUMN", JTYPE_INT, SQLITE_MAX_COLUMN},
{"SQLITE_LIMIT_EXPR_DEPTH", JTYPE_INT, SQLITE_LIMIT_EXPR_DEPTH},
{"SQLITE_MAX_EXPR_DEPTH", JTYPE_INT, SQLITE_MAX_EXPR_DEPTH},
{"SQLITE_LIMIT_COMPOUND_SELECT", JTYPE_INT, SQLITE_LIMIT_COMPOUND_SELECT},
{"SQLITE_MAX_COMPOUND_SELECT", JTYPE_INT, SQLITE_MAX_COMPOUND_SELECT},
{"SQLITE_LIMIT_VDBE_OP", JTYPE_INT, SQLITE_LIMIT_VDBE_OP},
{"SQLITE_MAX_VDBE_OP", JTYPE_INT, SQLITE_MAX_VDBE_OP},
{"SQLITE_LIMIT_FUNCTION_ARG", JTYPE_INT, SQLITE_LIMIT_FUNCTION_ARG},
{"SQLITE_MAX_FUNCTION_ARG", JTYPE_INT, SQLITE_MAX_FUNCTION_ARG},
{"SQLITE_LIMIT_ATTACHED", JTYPE_INT, SQLITE_LIMIT_ATTACHED},
{"SQLITE_MAX_ATTACHED", JTYPE_INT, SQLITE_MAX_ATTACHED},
{"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_LIMIT_LIKE_PATTERN_LENGTH},
{"SQLITE_MAX_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_MAX_LIKE_PATTERN_LENGTH},
{"SQLITE_LIMIT_VARIABLE_NUMBER", JTYPE_INT, SQLITE_LIMIT_VARIABLE_NUMBER},
{"SQLITE_MAX_VARIABLE_NUMBER", JTYPE_INT, SQLITE_MAX_VARIABLE_NUMBER},
{"SQLITE_LIMIT_TRIGGER_DEPTH", JTYPE_INT, SQLITE_LIMIT_TRIGGER_DEPTH},
{"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH},
{"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS},
{"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS},
{0,0}
};
jfieldID fieldId;
jclass klazz;
const ConfigFlagEntry * pConfFlag;
if( 0==sqlite3_threadsafe() ){
(*env)->FatalError(env, "sqlite3 was not built with SQLITE_THREADSAFE.");
return;
}
memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));
if( (*env)->GetJavaVM(env, &SJG.jvm) ){
(*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible.");
return;
}
/* Grab references to various global classes and objects... */
SJG.g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object"));
EXCEPTION_IS_FATAL("Error getting reference to Object class.");
SJG.g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long"));
EXCEPTION_IS_FATAL("Error getting reference to Long class.");
SJG.g.ctorLong1 = (*env)->GetMethodID(env, SJG.g.cLong,
"<init>", "(J)V");
EXCEPTION_IS_FATAL("Error getting reference to Long constructor.");
SJG.g.cString = REF_G((*env)->FindClass(env,"java/lang/String"));
EXCEPTION_IS_FATAL("Error getting reference to String class.");
SJG.g.ctorStringBA =
(*env)->GetMethodID(env, SJG.g.cString,
"<init>", "([BLjava/nio/charset/Charset;)V");
EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor.");
SJG.g.stringGetBytes =
(*env)->GetMethodID(env, SJG.g.cString,
"getBytes", "(Ljava/nio/charset/Charset;)[B");
EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset).");
{ /* StandardCharsets.UTF_8 */
jfieldID fUtf8;
klazz = (*env)->FindClass(env,"java/nio/charset/StandardCharsets");
EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets class.");
fUtf8 = (*env)->GetStaticFieldID(env, klazz, "UTF_8",
"Ljava/nio/charset/Charset;");
EXCEPTION_IS_FATAL("Error getting StandardCharsets.UTF_8 field.");
SJG.g.oCharsetUtf8 =
REF_G((*env)->GetStaticObjectField(env, klazz, fUtf8));
EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8.");
UNREF_L(klazz);
}
#ifdef SQLITE_ENABLE_FTS5
klazz = (*env)->FindClass(env, "org/sqlite/jni/Fts5PhraseIter");
EXCEPTION_IS_FATAL("Error getting reference to org.sqlite.jni.Fts5PhraseIter.");
SJG.fts5.jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J");
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field.");
SJG.fts5.jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "b", "J");
EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field.");
UNREF_L(klazz);
#endif
SJG.envCache.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
s3jni_oom_check( SJG.envCache.mutex );
SJG.perDb.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
s3jni_oom_check( SJG.perDb.mutex );
SJG.autoExt.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
s3jni_oom_check( SJG.autoExt.mutex );
#if S3JNI_METRICS_MUTEX
SJG.metrics.mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
s3jni_oom_check( SJG.metrics.mutex );
#endif
sqlite3_shutdown()
/* So that it becomes legal for Java-level code to call
** sqlite3_config(), if it's ever implemented. */;
/* Set up static "consts" of the SQLite3Jni class. */
for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){
char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I";
fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig);
EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class.");
assert(fieldId);
switch(pConfFlag->jtype){
case JTYPE_INT:
(*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value);
break;
case JTYPE_BOOL:
(*env)->SetStaticBooleanField(env, jKlazz, fieldId,
pConfFlag->value ? JNI_TRUE : JNI_FALSE);
break;
}
EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed.");
}
}