Merge all the latest enhancements and fixes from trunk into the jsonb branch.

FossilOrigin-Name: ba91408f4c044feda003ef93784ccefb619f99ab64379ced481ee8e9e890fd41
This commit is contained in:
drh 2023-11-15 13:23:40 +00:00
commit fb57c8a932
25 changed files with 1606 additions and 286 deletions

View File

@ -81,6 +81,7 @@ $(bin.version-info): $(dir.tool)/version-info.c $(sqlite3.h) $(dir.top)/Makefile
# Be explicit about which Java files to compile so that we can work on # Be explicit about which Java files to compile so that we can work on
# in-progress files without requiring them to be in a compilable statae. # in-progress files without requiring them to be in a compilable statae.
JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
Experimental.java \
NotNull.java \ NotNull.java \
Nullable.java \ Nullable.java \
) $(patsubst %,$(dir.src.capi)/%,\ ) $(patsubst %,$(dir.src.capi)/%,\
@ -93,7 +94,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
CollationNeededCallback.java \ CollationNeededCallback.java \
CommitHookCallback.java \ CommitHookCallback.java \
ConfigLogCallback.java \ ConfigLogCallback.java \
ConfigSqllogCallback.java \ ConfigSqlLogCallback.java \
NativePointerHolder.java \ NativePointerHolder.java \
OutputPointer.java \ OutputPointer.java \
PrepareMultiCallback.java \ PrepareMultiCallback.java \
@ -322,7 +323,7 @@ test-one: $(test.deps)
$(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags) $(bin.java) $(test.flags.jvm) org.sqlite.jni.wrapper1.Tester2 $(Tester2.flags)
test-sqllog: $(test.deps) test-sqllog: $(test.deps)
@echo "Testing with -sqllog..." @echo "Testing with -sqllog..."
$(bin.java) $(test.flags.jvm) -sqllog $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 $(Tester1.flags) -sqllog
test-mt: $(test.deps) test-mt: $(test.deps)
@echo "Testing in multi-threaded mode:"; @echo "Testing in multi-threaded mode:";
$(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \ $(bin.java) $(test.flags.jvm) org.sqlite.jni.capi.Tester1 \

View File

@ -16,9 +16,8 @@ Technical support is available in the forum:
> **FOREWARNING:** this subproject is very much in development and > **FOREWARNING:** this subproject is very much in development and
subject to any number of changes. Please do not rely on any subject to any number of changes. Please do not rely on any
information about its API until this disclaimer is removed. The JNI information about its API until this disclaimer is removed. The JNI
bindings released with version 3.43 are a "tech preview" and 3.44 bindings released with version 3.43 are a "tech preview." Once
will be "final," at which point strong backward compatibility finalized, strong backward compatibility guarantees will apply.
guarantees will apply.
Project goals/requirements: Project goals/requirements:
@ -43,11 +42,13 @@ Non-goals:
- Creation of high-level OO wrapper APIs. Clients are free to create - Creation of high-level OO wrapper APIs. Clients are free to create
them off of the C-style API. them off of the C-style API.
- Virtual tables are unlikely to be supported due to the amount of
glue code needed to fit them into Java.
- Support for mixed-mode operation, where client code accesses SQLite - Support for mixed-mode operation, where client code accesses SQLite
both via the Java-side API and the C API via their own native both via the Java-side API and the C API via their own native
code. In such cases, proxy functionalities (primarily callback code. Such cases would be a minefield of potential mis-interactions
handler wrappers of all sorts) may fail because the C-side use of between this project's JNI bindings and mixed-mode client code.
the SQLite APIs will bypass those proxies.
Hello World Hello World
@ -123,15 +124,13 @@ sensible default argument values. In all such cases they are thin
proxies around the corresponding C APIs and do not introduce new proxies around the corresponding C APIs and do not introduce new
semantics. semantics.
In some very few cases, Java-specific capabilities have been added in In a few cases, Java-specific capabilities have been added in
new APIs, all of which have "_java" somewhere in their names. new APIs, all of which have "_java" somewhere in their names.
Examples include: Examples include:
- `sqlite3_result_java_object()` - `sqlite3_result_java_object()`
- `sqlite3_column_java_object()` - `sqlite3_column_java_object()`
- `sqlite3_column_java_casted()`
- `sqlite3_value_java_object()` - `sqlite3_value_java_object()`
- `sqlite3_value_java_casted()`
which, as one might surmise, collectively enable the passing of which, as one might surmise, collectively enable the passing of
arbitrary Java objects from user-defined SQL functions through to the arbitrary Java objects from user-defined SQL functions through to the
@ -150,6 +149,9 @@ pending statements have been closed. Be aware that Java garbage
collection _cannot_ close a database or finalize a prepared statement. collection _cannot_ close a database or finalize a prepared statement.
Those things require explicit API calls. Those things require explicit API calls.
Classes for which it is sensible support Java's `AutoCloseable`
interface so can be used with try-with-resources constructs.
Golden Rule #2: _Never_ Throw from Callbacks (Unless...) Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
------------------------------------------------------------------------ ------------------------------------------------------------------------
@ -159,14 +161,15 @@ retain C-like semantics. For example, they are not permitted to throw
or propagate exceptions and must return error information (if any) via or propagate exceptions and must return error information (if any) via
result codes or `null`. The only cases where the C-style APIs may result codes or `null`. The only cases where the C-style APIs may
throw is through client-side misuse, e.g. passing in a null where it throw is through client-side misuse, e.g. passing in a null where it
shouldn't be used. The APIs clearly mark function parameters which may cause a `NullPointerException`. The APIs clearly mark function
should not be null, but does not actively defend itself against such parameters which should not be null, but does not generally actively
misuse. Some C-style APIs explicitly accept `null` as a no-op for defend itself against such misuse. Some C-style APIs explicitly accept
usability's sake, and some of the JNI APIs deliberately return an `null` as a no-op for usability's sake, and some of the JNI APIs
error code, instead of segfaulting, when passed a `null`. deliberately return an error code, instead of segfaulting, when passed
a `null`.
Client-defined callbacks _must never throw exceptions_ unless _very Client-defined callbacks _must never throw exceptions_ unless _very
explicitly documented_ as being throw-safe. Exceptions are generally explitly documented_ as being throw-safe. Exceptions are generally
reserved for higher-level bindings which are constructed to reserved for higher-level bindings which are constructed to
specifically deal with them and ensure that they do not leak C-level specifically deal with them and ensure that they do not leak C-level
resources. In some cases, callback handlers are permitted to throw, in resources. In some cases, callback handlers are permitted to throw, in
@ -292,14 +295,14 @@ int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
`SQLFunction` is not used directly, but is instead instantiated via `SQLFunction` is not used directly, but is instead instantiated via
one of its three subclasses: one of its three subclasses:
- `SQLFunction.Scalar` implements simple scalar functions using but a - `ScalarFunction` implements simple scalar functions using but a
single callback. single callback.
- `SQLFunction.Aggregate` implements aggregate functions using two - `AggregateFunction` implements aggregate functions using two
callbacks. callbacks.
- `SQLFunction.Window` implements window functions using four - `WindowFunction` implements window functions using four
callbacks. callbacks.
Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/Tester1.java) for Search [`Tester1.java`](/file/ext/jni/src/org/sqlite/jni/capi/Tester1.java) for
`SQLFunction` for how it's used. `SQLFunction` for how it's used.
Reminder: see the disclaimer at the top of this document regarding the Reminder: see the disclaimer at the top of this document regarding the

View File

@ -15,13 +15,14 @@
/* /*
** If you found this comment by searching the code for ** If you found this comment by searching the code for
** CallStaticObjectMethod then you're the victim of an OpenJDK bug: ** CallStaticObjectMethod because it appears in console output then
** you're probably the victim of an OpenJDK bug:
** **
** https://bugs.openjdk.org/browse/JDK-8130659 ** https://bugs.openjdk.org/browse/JDK-8130659
** **
** It's known to happen with OpenJDK v8 but not with v19. ** It's known to happen with OpenJDK v8 but not with v19. It was
** ** triggered by this code long before it made any use of
** This code does not use JNI's CallStaticObjectMethod(). ** CallStaticObjectMethod().
*/ */
/* /*
@ -660,10 +661,14 @@ struct S3JniGlobalType {
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#nio_support
We only store a ref to the following if JNI support for We only store a ref to byteBuffer.klazz if JNI support for
ByteBuffer is available (which we determine during static init). ByteBuffer is available (which we determine during static init).
*/ */
jclass cByteBuffer /* global ref to java.nio.ByteBuffer */; struct {
jclass klazz /* global ref to java.nio.ByteBuffer */;
jmethodID midAlloc /* ByteBuffer.allocateDirect() */;
jmethodID midLimit /* ByteBuffer.limit() */;
} byteBuffer;
} g; } g;
/* /*
** The list of Java-side auto-extensions ** The list of Java-side auto-extensions
@ -870,6 +875,58 @@ static jbyte * s3jni__jbyteArray_bytes2(JNIEnv * const env, jbyteArray jBA, jsiz
#define s3jni_jbyteArray_commit(jByteArray,jBytes) \ #define s3jni_jbyteArray_commit(jByteArray,jBytes) \
if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT) if( jBytes ) (*env)->ReleaseByteArrayElements(env, jByteArray, jBytes, JNI_COMMIT)
/*
** If jbb is-a java.nio.Buffer object and the JNI environment supports
** it, *pBuf is set to the buffer's memory and *pN is set to its
** limit() (as opposed to its capacity()). If jbb is NULL, not a
** Buffer, or the JNI environment does not support that operation,
** *pBuf is set to 0 and *pN is set to 0.
**
** Note that the length of the buffer can be larger than SQLITE_LIMIT
** but this function does not know what byte range of the buffer is
** required so cannot check for that violation. The caller is required
** to ensure that any to-be-bind()ed range fits within SQLITE_LIMIT.
**
** Sidebar: it is unfortunate that we cannot get ByteBuffer.limit()
** via a JNI method like we can for ByteBuffer.capacity(). We instead
** have to call back into Java to get the limit(). Depending on how
** the ByteBuffer is used, the limit and capacity might be the same,
** but when reusing a buffer, the limit may well change whereas the
** capacity is fixed. The problem with, e.g., read()ing blob data to a
** ByteBuffer's memory based on its capacity is that Java-level code
** is restricted to accessing the range specified in
** ByteBuffer.limit(). If we were to honor only the capacity, we
** could end up writing to, or reading from, parts of a ByteBuffer
** which client code itself cannot access without explicitly modifying
** the limit. The penalty we pay for this correctness is that we must
** call into Java to get the limit() of every ByteBuffer we work with.
**
** An alternative to having to call into ByteBuffer.limit() from here
** would be to add private native impls of all ByteBuffer-using
** methods, each of which adds a jint parameter which _must_ be set to
** theBuffer.limit() by public Java APIs which use those private impls
** to do the real work.
*/
static void s3jni__get_nio_buffer(JNIEnv * const env, jobject jbb, void **pBuf, jint * pN ){
*pBuf = 0;
*pN = 0;
if( jbb ){
*pBuf = (*env)->GetDirectBufferAddress(env, jbb);
if( *pBuf ){
/*
** Maintenance reminder: do not use
** (*env)->GetDirectBufferCapacity(env,jbb), even though it
** would be much faster, for reasons explained in this
** function's comments.
*/
*pN = (*env)->CallIntMethod(env, jbb, SJG.g.byteBuffer.midLimit);
S3JniExceptionIsFatal("Error calling ByteBuffer.limit() method.");
}
}
}
#define s3jni_get_nio_buffer(JOBJ,vpOut,jpOut) \
s3jni__get_nio_buffer(env,(JOBJ),(vpOut),(jpOut))
/* /*
** Returns the current JNIEnv object. Fails fatally if it cannot find ** Returns the current JNIEnv object. Fails fatally if it cannot find
** the object. ** the object.
@ -1068,6 +1125,47 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p,
return rv; return rv;
} }
/*
** Creates a new ByteBuffer instance with a capacity of n. assert()s
** that SJG.g.byteBuffer.klazz is not 0 and n>0.
*/
static jobject s3jni__new_ByteBuffer(JNIEnv * const env, int n){
jobject rv = 0;
assert( SJG.g.byteBuffer.klazz );
assert( SJG.g.byteBuffer.midAlloc );
assert( n > 0 );
rv = (*env)->CallStaticObjectMethod(env, SJG.g.byteBuffer.klazz,
SJG.g.byteBuffer.midAlloc, (jint)n);
S3JniIfThrew {
S3JniExceptionReport;
S3JniExceptionClear;
}
s3jni_oom_check( rv );
return rv;
}
/*
** If n>0 and sqlite3_jni_supports_nio() is true then this creates a
** new ByteBuffer object and copies n bytes from p to it. Returns NULL
** if n is 0, sqlite3_jni_supports_nio() is false, or on allocation
** error (unless fatal alloc failures are enabled).
*/
static jobject s3jni__blob_to_ByteBuffer(JNIEnv * const env,
const void * p, int n){
jobject rv = NULL;
assert( n >= 0 );
if( 0==n || !SJG.g.byteBuffer.klazz ){
return NULL;
}
rv = s3jni__new_ByteBuffer(env, n);
if( rv ){
void * tgt = (*env)->GetDirectBufferAddress(env, rv);
memcpy(tgt, p, (size_t)n);
}
return rv;
}
/* /*
** Requires jx to be a Throwable. Calls its toString() method and ** Requires jx to be a Throwable. Calls its toString() method and
** returns its value converted to a UTF-8 string. The caller owns the ** returns its value converted to a UTF-8 string. The caller owns the
@ -1479,13 +1577,13 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
** argument is a Java sqlite3 object, as this operation only has void ** argument is a Java sqlite3 object, as this operation only has void
** pointers to work with. ** pointers to work with.
*/ */
#define PtrGet_T(T,OBJ) (T*)NativePointerHolder_get(OBJ, S3JniNph(T)) #define PtrGet_T(T,JOBJ) (T*)NativePointerHolder_get((JOBJ), S3JniNph(T))
#define PtrGet_sqlite3(OBJ) PtrGet_T(sqlite3, OBJ) #define PtrGet_sqlite3(JOBJ) PtrGet_T(sqlite3, (JOBJ))
#define PtrGet_sqlite3_backup(OBJ) PtrGet_T(sqlite3_backup, OBJ) #define PtrGet_sqlite3_backup(JOBJ) PtrGet_T(sqlite3_backup, (JOBJ))
#define PtrGet_sqlite3_blob(OBJ) PtrGet_T(sqlite3_blob, OBJ) #define PtrGet_sqlite3_blob(JOBJ) PtrGet_T(sqlite3_blob, (JOBJ))
#define PtrGet_sqlite3_context(OBJ) PtrGet_T(sqlite3_context, OBJ) #define PtrGet_sqlite3_context(JOBJ) PtrGet_T(sqlite3_context, (JOBJ))
#define PtrGet_sqlite3_stmt(OBJ) PtrGet_T(sqlite3_stmt, OBJ) #define PtrGet_sqlite3_stmt(JOBJ) PtrGet_T(sqlite3_stmt, (JOBJ))
#define PtrGet_sqlite3_value(OBJ) PtrGet_T(sqlite3_value, OBJ) #define PtrGet_sqlite3_value(JOBJ) PtrGet_T(sqlite3_value, (JOBJ))
/* /*
** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct ** LongPtrGet_T(X,Y) expects X to be an unqualified sqlite3 struct
** type name and Y to be a native pointer to such an object in the ** type name and Y to be a native pointer to such an object in the
@ -1505,12 +1603,12 @@ static void * NativePointerHolder__get(JNIEnv * env, jobject jNph,
** a difference of microseconds (i.e. below our testing measurement ** a difference of microseconds (i.e. below our testing measurement
** threshold) might add up. ** threshold) might add up.
*/ */
#define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)(JLongAsPtr)) #define LongPtrGet_T(T,JLongAsPtr) (T*)((intptr_t)((JLongAsPtr)))
#define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,JLongAsPtr) #define LongPtrGet_sqlite3(JLongAsPtr) LongPtrGet_T(sqlite3,(JLongAsPtr))
#define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,JLongAsPtr) #define LongPtrGet_sqlite3_backup(JLongAsPtr) LongPtrGet_T(sqlite3_backup,(JLongAsPtr))
#define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,JLongAsPtr) #define LongPtrGet_sqlite3_blob(JLongAsPtr) LongPtrGet_T(sqlite3_blob,(JLongAsPtr))
#define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,JLongAsPtr) #define LongPtrGet_sqlite3_stmt(JLongAsPtr) LongPtrGet_T(sqlite3_stmt,(JLongAsPtr))
#define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,JLongAsPtr) #define LongPtrGet_sqlite3_value(JLongAsPtr) LongPtrGet_T(sqlite3_value,(JLongAsPtr))
/* /*
** Extracts the new S3JniDb instance from the free-list, or allocates ** Extracts the new S3JniDb instance from the free-list, or allocates
** one if needed, associates it with pDb, and returns. Returns NULL ** one if needed, associates it with pDb, and returns. Returns NULL
@ -1897,13 +1995,16 @@ error_oom:
/* /*
** Requires that jCx and jArgv are sqlite3_context ** Requires that jCx and jArgv are sqlite3_context
** resp. array-of-sqlite3_value values initialized by udf_args(). This ** resp. array-of-sqlite3_value values initialized by udf_args(). The
** latter will be 0-and-NULL for UDF types with no arguments. This
** function zeroes out the nativePointer member of jCx and each entry ** function zeroes out the nativePointer member of jCx and each entry
** in jArgv. This is a safety-net precaution to avoid undefined ** in jArgv. This is a safety-net precaution to avoid undefined
** behavior if a Java-side UDF holds a reference to one of its ** behavior if a Java-side UDF holds a reference to its context or one
** arguments. This MUST be called from any function which successfully ** of its arguments. This MUST be called from any function which
** calls udf_args(), after calling the corresponding UDF and checking ** successfully calls udf_args(), after calling the corresponding UDF
** its exception status. It MUST NOT be called in any other case. ** and checking its exception status, or which Java-wraps a
** sqlite3_context for use with a UDF(ish) call. It MUST NOT be called
** in any other case.
*/ */
static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){ static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
int i = 0; int i = 0;
@ -1911,8 +2012,29 @@ static void udf_unargs(JNIEnv *env, jobject jCx, int argc, jobjectArray jArgv){
NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0); NativePointerHolder_set(S3JniNph(sqlite3_context), jCx, 0);
for( ; i < argc; ++i ){ for( ; i < argc; ++i ){
jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i); jobject jsv = (*env)->GetObjectArrayElement(env, jArgv, i);
assert(jsv); /*
NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0); ** There is a potential Java-triggerable case of Undefined
** Behavior here, but it would require intentional misuse of the
** API:
**
** If a Java UDF grabs an sqlite3_value from its argv and then
** assigns that element to null, it becomes unreachable to us so
** we cannot clear out its pointer. That Java-side object's
** getNativePointer() will then refer to a stale value, so passing
** it into (e.g.) sqlite3_value_SOMETHING() would invoke UB.
**
** High-level wrappers can avoid that possibility if they do not
** expose sqlite3_value directly to clients (as is the case in
** org.sqlite.jni.wrapper1.SqlFunction).
**
** One potential (but expensive) workaround for this would be to
** privately store a duplicate argv array in each sqlite3_context
** wrapper object, and clear the native pointers from that copy.
*/
assert(jsv && "Someone illegally modified a UDF argument array.");
if( jsv ){
NativePointerHolder_set(S3JniNph(sqlite3_value), jsv, 0);
}
} }
} }
@ -2001,6 +2123,7 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
rc = udf_report_exception(env, isFinal, cx, s->zFuncName, rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
zFuncType); zFuncType);
} }
udf_unargs(env, jcx, 0, 0);
S3JniUnrefLocal(jcx); S3JniUnrefLocal(jcx);
}else{ }else{
if( isFinal ) sqlite3_result_error_nomem(cx); if( isFinal ) sqlite3_result_error_nomem(cx);
@ -2408,6 +2531,112 @@ S3JniApi(sqlite3_bind_blob(),jint,1bind_1blob)(
return (jint)rc; return (jint)rc;
} }
/**
Helper for use with s3jni_setup_nio_args().
*/
struct S3JniNioArgs {
jobject jBuf; /* input - ByteBuffer */
jint iOffset; /* input - byte offset */
jint iHowMany; /* input - byte count to bind/read/write */
jint nBuf; /* output - jBuf's buffer size */
void * p; /* output - jBuf's buffer memory */
void * pStart; /* output - offset of p to bind/read/write */
int nOut; /* output - number of bytes from pStart to bind/read/write */
};
typedef struct S3JniNioArgs S3JniNioArgs;
static const S3JniNioArgs S3JniNioArgs_empty = {
0,0,0,0,0,0,0
};
/*
** Internal helper for sqlite3_bind_nio_buffer(),
** sqlite3_result_nio_buffer(), and similar methods which take a
** ByteBuffer object as either input or output. Populates pArgs and
** returns 0 on success, non-0 if the operation should fail. The
** caller is required to check for SJG.g.byteBuffer.klazz!=0 before calling
** this and reporting it in a way appropriate for that routine. This
** function may assert() that SJG.g.byteBuffer.klazz is not 0.
**
** The (jBuffer, iOffset, iHowMany) arguments are the (ByteBuffer, offset,
** length) arguments to the bind/result method.
**
** If iHowMany is negative then it's treated as "until the end" and
** the calculated slice is trimmed to fit if needed. If iHowMany is
** positive and extends past the end of jBuffer then SQLITE_ERROR is
** returned.
**
** Returns 0 if everything looks to be in order, else some SQLITE_...
** result code
*/
static int s3jni_setup_nio_args(
JNIEnv *env, S3JniNioArgs * pArgs,
jobject jBuffer, jint iOffset, jint iHowMany
){
jlong iEnd = 0;
const int bAllowTruncate = iHowMany<0;
*pArgs = S3JniNioArgs_empty;
pArgs->jBuf = jBuffer;
pArgs->iOffset = iOffset;
pArgs->iHowMany = iHowMany;
assert( SJG.g.byteBuffer.klazz );
if( pArgs->iOffset<0 ){
return SQLITE_ERROR
/* SQLITE_MISUSE or SQLITE_RANGE would fit better but we use
SQLITE_ERROR for consistency with the code documented for a
negative target blob offset in sqlite3_blob_read/write(). */;
}
s3jni_get_nio_buffer(pArgs->jBuf, &pArgs->p, &pArgs->nBuf);
if( !pArgs->p ){
return SQLITE_MISUSE;
}else if( pArgs->iOffset>=pArgs->nBuf ){
pArgs->pStart = 0;
pArgs->nOut = 0;
return 0;
}
assert( pArgs->nBuf > 0 );
assert( pArgs->iOffset < pArgs->nBuf );
iEnd = pArgs->iHowMany<0
? pArgs->nBuf - pArgs->iOffset
: pArgs->iOffset + pArgs->iHowMany;
if( iEnd>(jlong)pArgs->nBuf ){
if( bAllowTruncate ){
iEnd = pArgs->nBuf - pArgs->iOffset;
}else{
return SQLITE_ERROR
/* again: for consistency with blob_read/write(), though
SQLITE_MISUSE or SQLITE_RANGE would be a better fit. */;
}
}
if( iEnd - pArgs->iOffset > (jlong)SQLITE_MAX_LENGTH ){
return SQLITE_TOOBIG;
}
assert( pArgs->iOffset >= 0 );
assert( iEnd > pArgs->iOffset );
pArgs->pStart = pArgs->p + pArgs->iOffset;
pArgs->nOut = (int)(iEnd - pArgs->iOffset);
assert( pArgs->nOut > 0 );
assert( (pArgs->pStart + pArgs->nOut) <= (pArgs->p + pArgs->nBuf) );
return 0;
}
S3JniApi(sqlite3_bind_nio_buffer(),jint,1bind_1nio_1buffer)(
JniArgsEnvClass, jobject jpStmt, jint ndx, jobject jBuffer,
jint iOffset, jint iN
){
sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt);
S3JniNioArgs args;
int rc;
if( !pStmt || !SJG.g.byteBuffer.klazz ) return SQLITE_MISUSE;
rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
if(rc){
return rc;
}else if( !args.pStart || !args.nOut ){
return sqlite3_bind_null(pStmt, ndx);
}
return sqlite3_bind_blob( pStmt, (int)ndx, args.pStart,
args.nOut, SQLITE_TRANSIENT );
}
S3JniApi(sqlite3_bind_double(),jint,1bind_1double)( S3JniApi(sqlite3_bind_double(),jint,1bind_1double)(
JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val JniArgsEnvClass, jlong jpStmt, jint ndx, jdouble val
){ ){
@ -2622,6 +2851,30 @@ S3JniApi(sqlite3_blob_read(),jint,1blob_1read)(
return rc; return rc;
} }
S3JniApi(sqlite3_blob_read_nio_buffer(),jint,1blob_1read_1nio_1buffer)(
JniArgsEnvClass, jlong jpBlob, jint iSrcOff, jobject jBB, jint iTgtOff, jint iHowMany
){
sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
S3JniNioArgs args;
int rc;
if( !b || !SJG.g.byteBuffer.klazz || iHowMany<0 ){
return SQLITE_MISUSE;
}else if( iTgtOff<0 || iSrcOff<0 ){
return SQLITE_ERROR
/* for consistency with underlying sqlite3_blob_read() */;
}else if( 0==iHowMany ){
return 0;
}
rc = s3jni_setup_nio_args(env, &args, jBB, iTgtOff, iHowMany);
if(rc){
return rc;
}else if( !args.pStart || !args.nOut ){
return 0;
}
assert( args.iHowMany>0 );
return sqlite3_blob_read( b, args.pStart, (int)args.nOut, (int)iSrcOff );
}
S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)( S3JniApi(sqlite3_blob_reopen(),jint,1blob_1reopen)(
JniArgsEnvClass, jlong jpBlob, jlong iNewRowId JniArgsEnvClass, jlong jpBlob, jlong iNewRowId
){ ){
@ -2643,6 +2896,29 @@ S3JniApi(sqlite3_blob_write(),jint,1blob_1write)(
return (jint)rc; return (jint)rc;
} }
S3JniApi(sqlite3_blob_write_nio_buffer(),jint,1blob_1write_1nio_1buffer)(
JniArgsEnvClass, jlong jpBlob, jint iTgtOff, jobject jBB, jint iSrcOff, jint iHowMany
){
sqlite3_blob * const b = LongPtrGet_sqlite3_blob(jpBlob);
S3JniNioArgs args;
int rc;
if( !b || !SJG.g.byteBuffer.klazz ){
return SQLITE_MISUSE;
}else if( iTgtOff<0 || iSrcOff<0 ){
return SQLITE_ERROR
/* for consistency with underlying sqlite3_blob_write() */;
}else if( 0==iHowMany ){
return 0;
}
rc = s3jni_setup_nio_args(env, &args, jBB, iSrcOff, iHowMany);
if(rc){
return rc;
}else if( !args.pStart || !args.nOut ){
return 0;
}
return sqlite3_blob_write( b, args.pStart, (int)args.nOut, (int)iTgtOff );
}
/* Central C-to-Java busy handler proxy. */ /* Central C-to-Java busy handler proxy. */
static int s3jni_busy_handler(void* pState, int n){ static int s3jni_busy_handler(void* pState, int n){
S3JniDb * const ps = (S3JniDb *)pState; S3JniDb * const ps = (S3JniDb *)pState;
@ -2911,6 +3187,21 @@ S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)(
return rv; return rv;
} }
S3JniApi(sqlite3_column_nio_buffer(),jobject,1column_1nio_1buffer)(
JniArgsEnvClass, jobject jStmt, jint ndx
){
sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jStmt);
jobject rv = 0;
if( stmt ){
const void * const p = sqlite3_column_blob(stmt, (int)ndx);
if( p ){
const int n = sqlite3_column_bytes(stmt, (int)ndx);
rv = s3jni__blob_to_ByteBuffer(env, p, n);
}
}
return rv;
}
S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)( S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
JniArgsEnvClass, jobject jpStmt, jint ndx JniArgsEnvClass, jobject jpStmt, jint ndx
){ ){
@ -3088,8 +3379,9 @@ S3JniApi(sqlite3_complete(),jint,1complete)(
return rc; return rc;
} }
S3JniApi(sqlite3_config() /*for a small subset of options.*/, S3JniApi(sqlite3_config() /*for a small subset of options.*/
jint,1config__I)(JniArgsEnvClass, jint n){ sqlite3_config__enable()/* internal name to avoid name-mangling issues*/,
jint,1config_1_1enable)(JniArgsEnvClass, jint n){
switch( n ){ switch( n ){
case SQLITE_CONFIG_SINGLETHREAD: case SQLITE_CONFIG_SINGLETHREAD:
case SQLITE_CONFIG_MULTITHREAD: case SQLITE_CONFIG_MULTITHREAD:
@ -3119,8 +3411,9 @@ static void s3jni_config_log(void *ignored, int errCode, const char *z){
} }
} }
S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */, S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_LOG */
jint, 1config__Lorg_sqlite_jni_ConfigLogCallback_2 sqlite3_config__config_log() /* internal name */,
jint, 1config_1_1CONFIG_1LOG
)(JniArgsEnvClass, jobject jLog){ )(JniArgsEnvClass, jobject jLog){
S3JniHook * const pHook = &SJG.hook.configlog; S3JniHook * const pHook = &SJG.hook.configlog;
int rc = 0; int rc = 0;
@ -3194,9 +3487,10 @@ void sqlite3_init_sqllog(void){
} }
#endif #endif
S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */, S3JniApi(sqlite3_config() /* for SQLITE_CONFIG_SQLLOG */
jint, 1config__Lorg_sqlite_jni_ConfigSqllogCallback_2)( sqlite3_config__SQLLOG() /*internal name*/,
JniArgsEnvClass, jobject jLog){ jint, 1config_1_1SQLLOG
)(JniArgsEnvClass, jobject jLog){
#ifndef SQLITE_ENABLE_SQLLOG #ifndef SQLITE_ENABLE_SQLLOG
return SQLITE_MISUSE; return SQLITE_MISUSE;
#else #else
@ -3675,7 +3969,9 @@ S3JniApi(sqlite3_is_interrupted(),jboolean,1is_1interrupted)(
** any resources owned by that cache entry and making that slot ** any resources owned by that cache entry and making that slot
** available for re-use. ** available for re-use.
*/ */
JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){ S3JniApi(sqlite3_java_uncache_thread(), jboolean, 1java_1uncache_1thread)(
JniArgsEnvClass
){
int rc; int rc;
S3JniEnv_mutex_enter; S3JniEnv_mutex_enter;
rc = S3JniEnv_uncache(env); rc = S3JniEnv_uncache(env);
@ -3683,8 +3979,26 @@ JniDecl(jboolean,1java_1uncache_1thread)(JniArgsEnvClass){
return rc ? JNI_TRUE : JNI_FALSE; return rc ? JNI_TRUE : JNI_FALSE;
} }
JniDecl(jboolean,1jni_1supports_1nio)(JniArgsEnvClass){ S3JniApi(sqlite3_jni_db_error(), jint, 1jni_1db_1error)(
return SJG.g.cByteBuffer ? JNI_TRUE : JNI_FALSE; JniArgsEnvClass, jobject jDb, jint jRc, jstring jStr
){
S3JniDb * const ps = S3JniDb_from_java(jDb);
int rc = SQLITE_MISUSE;
if( ps ){
char *zStr;
zStr = jStr
? s3jni_jstring_to_utf8( jStr, 0)
: NULL;
rc = s3jni_db_error( ps->pDb, (int)jRc, zStr );
sqlite3_free(zStr);
}
return rc;
}
S3JniApi(sqlite3_jni_supports_nio(), jboolean,1jni_1supports_1nio)(
JniArgsEnvClass
){
return SJG.g.byteBuffer.klazz ? JNI_TRUE : JNI_FALSE;
} }
@ -3861,10 +4175,11 @@ S3JniApi(sqlite3_open_v2(),jint,1open_1v2)(
} }
/* Proxy for the sqlite3_prepare[_v2/3]() family. */ /* Proxy for the sqlite3_prepare[_v2/3]() family. */
jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env, jclass self, static jint sqlite3_jni_prepare_v123( int prepVersion, JNIEnv * const env,
jlong jpDb, jbyteArray baSql, jclass self,
jint nMax, jint prepFlags, jlong jpDb, jbyteArray baSql,
jobject jOutStmt, jobject outTail){ jint nMax, jint prepFlags,
jobject jOutStmt, jobject outTail){
sqlite3_stmt * pStmt = 0; sqlite3_stmt * pStmt = 0;
jobject jStmt = 0; jobject jStmt = 0;
const char * zTail = 0; const char * zTail = 0;
@ -4417,6 +4732,40 @@ S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
} }
} }
S3JniApi(sqlite3_result_nio_buffer(),void,1result_1nio_1buffer)(
JniArgsEnvClass, jobject jpCtx, jobject jBuffer,
jint iOffset, jint iN
){
sqlite3_context * pCx = PtrGet_sqlite3_context(jpCtx);
int rc;
S3JniNioArgs args;
if( !pCx ){
return;
}else if( !SJG.g.byteBuffer.klazz ){
sqlite3_result_error(
pCx, "This JVM does not support JNI access to ByteBuffers.", -1
);
return;
}
rc = s3jni_setup_nio_args(env, &args, jBuffer, iOffset, iN);
if(rc){
if( iOffset<0 ){
sqlite3_result_error(pCx, "Start index may not be negative.", -1);
}else if( SQLITE_TOOBIG==rc ){
sqlite3_result_error_toobig(pCx);
}else{
sqlite3_result_error(
pCx, "Invalid arguments to sqlite3_result_nio_buffer().", -1
);
}
}else if( !args.pStart || !args.nOut ){
sqlite3_result_null(pCx);
}else{
sqlite3_result_blob(pCx, args.pStart, args.nOut, SQLITE_TRANSIENT);
}
}
S3JniApi(sqlite3_result_null(),void,1result_1null)( S3JniApi(sqlite3_result_null(),void,1result_1null)(
JniArgsEnvClass, jobject jpCx JniArgsEnvClass, jobject jpCx
){ ){
@ -4945,6 +5294,21 @@ S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
: 0; : 0;
} }
S3JniApi(sqlite3_value_nio_buffer(),jobject,1value_1nio_1buffer)(
JniArgsEnvClass, jobject jVal
){
sqlite3_value * const sv = PtrGet_sqlite3_value(jVal);
jobject rv = 0;
if( sv ){
const void * const p = sqlite3_value_blob(sv);
if( p ){
const int n = sqlite3_value_bytes(sv);
rv = s3jni__blob_to_ByteBuffer(env, p, n);
}
}
return rv;
}
S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)( S3JniApi(sqlite3_value_text(),jbyteArray,1value_1text)(
JniArgsEnvClass, jlong jpSVal JniArgsEnvClass, jlong jpSVal
){ ){
@ -5951,10 +6315,19 @@ Java_org_sqlite_jni_capi_CApi_init(JniArgsEnvClass){
unsigned char buf[16] = {0}; unsigned char buf[16] = {0};
jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16); jobject bb = (*env)->NewDirectByteBuffer(env, buf, 16);
if( bb ){ if( bb ){
SJG.g.cByteBuffer = (*env)->GetObjectClass(env, bb); SJG.g.byteBuffer.klazz = S3JniRefGlobal((*env)->GetObjectClass(env, bb));
SJG.g.byteBuffer.midAlloc = (*env)->GetStaticMethodID(
env, SJG.g.byteBuffer.klazz, "allocateDirect", "(I)Ljava/nio/ByteBuffer;"
);
S3JniExceptionIsFatal("Error getting ByteBuffer.allocateDirect() method.");
SJG.g.byteBuffer.midLimit = (*env)->GetMethodID(
env, SJG.g.byteBuffer.klazz, "limit", "()I"
);
S3JniExceptionIsFatal("Error getting ByteBuffer.limit() method.");
S3JniUnrefLocal(bb); S3JniUnrefLocal(bb);
}else{ }else{
SJG.g.cByteBuffer = 0; SJG.g.byteBuffer.klazz = 0;
SJG.g.byteBuffer.midAlloc = 0;
} }
} }

View File

@ -785,6 +785,14 @@ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1java_1uncache_
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1supports_1nio
(JNIEnv *, jclass); (JNIEnv *, jclass);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_jni_db_error
* Signature: (Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1jni_1db_1error
(JNIEnv *, jclass, jobject, jint, jstring);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_aggregate_context * Method: sqlite3_aggregate_context
@ -881,6 +889,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1int64
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1java_1object
(JNIEnv *, jclass, jlong, jint, jobject); (JNIEnv *, jclass, jlong, jint, jobject);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_bind_nio_buffer
* Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;ILjava/nio/ByteBuffer;II)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1bind_1nio_1buffer
(JNIEnv *, jclass, jobject, jint, jobject, jint, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_bind_null * Method: sqlite3_bind_null
@ -985,6 +1001,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1open
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read
(JNIEnv *, jclass, jlong, jbyteArray, jint); (JNIEnv *, jclass, jlong, jbyteArray, jint);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_blob_read_nio_buffer
* Signature: (JILjava/nio/ByteBuffer;II)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1read_1nio_1buffer
(JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_blob_reopen * Method: sqlite3_blob_reopen
@ -1001,6 +1025,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1reopen
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write
(JNIEnv *, jclass, jlong, jbyteArray, jint); (JNIEnv *, jclass, jlong, jbyteArray, jint);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_blob_write_nio_buffer
* Signature: (JILjava/nio/ByteBuffer;II)I
*/
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1blob_1write_1nio_1buffer
(JNIEnv *, jclass, jlong, jint, jobject, jint, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_busy_handler * Method: sqlite3_busy_handler
@ -1097,6 +1129,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1bytes16
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1count
(JNIEnv *, jclass, jlong); (JNIEnv *, jclass, jlong);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_column_database_name
* Signature: (JI)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name
(JNIEnv *, jclass, jlong, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_column_decltype * Method: sqlite3_column_decltype
@ -1147,11 +1187,11 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1name
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_column_database_name * Method: sqlite3_column_nio_buffer
* Signature: (JI)Ljava/lang/String; * Signature: (Lorg/sqlite/jni/capi/sqlite3_stmt;I)Ljava/nio/ByteBuffer;
*/ */
JNIEXPORT jstring JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1database_1name JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1nio_1buffer
(JNIEnv *, jclass, jlong, jint); (JNIEnv *, jclass, jobject, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
@ -1243,26 +1283,26 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1complete
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_config * Method: sqlite3_config__enable
* Signature: (I)I * Signature: (I)I
*/ */
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__I JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1enable
(JNIEnv *, jclass, jint); (JNIEnv *, jclass, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_config * Method: sqlite3_config__CONFIG_LOG
* Signature: (Lorg/sqlite/jni/capi/ConfigSqllogCallback;)I * Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I
*/ */
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigSqllogCallback_2 JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1CONFIG_1LOG
(JNIEnv *, jclass, jobject); (JNIEnv *, jclass, jobject);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_config * Method: sqlite3_config__SQLLOG
* Signature: (Lorg/sqlite/jni/capi/ConfigLogCallback;)I * Signature: (Lorg/sqlite/jni/capi/ConfigSqlLogCallback;)I
*/ */
JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config__Lorg_sqlite_jni_capi_ConfigLogCallback_2 JNIEXPORT jint JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1config_1_1SQLLOG
(JNIEnv *, jclass, jobject); (JNIEnv *, jclass, jobject);
/* /*
@ -1721,6 +1761,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1int64
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1object
(JNIEnv *, jclass, jobject, jobject); (JNIEnv *, jclass, jobject, jobject);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_result_nio_buffer
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Ljava/nio/ByteBuffer;II)V
*/
JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1nio_1buffer
(JNIEnv *, jclass, jobject, jobject, jint, jint);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_result_null * Method: sqlite3_result_null
@ -2089,6 +2137,14 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1int64
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1java_1object
(JNIEnv *, jclass, jlong); (JNIEnv *, jclass, jlong);
/*
* Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_value_nio_buffer
* Signature: (Lorg/sqlite/jni/capi/sqlite3_value;)Ljava/nio/ByteBuffer;
*/
JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1value_1nio_1buffer
(JNIEnv *, jclass, jobject);
/* /*
* Class: org_sqlite_jni_capi_CApi * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_value_nochange * Method: sqlite3_value_nochange

View File

@ -0,0 +1,30 @@
/*
** 2023-09-27
**
** 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 houses the Experimental annotation for the sqlite3 C API.
*/
package org.sqlite.jni.annotation;
import java.lang.annotation.*;
/**
This annotation is for flagging methods, constructors, and types
which are expressly experimental and subject to any amount of
change or outright removal. Client code should not rely on such
features.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
ElementType.METHOD,
ElementType.CONSTRUCTOR,
ElementType.TYPE
})
public @interface Experimental{}

View File

@ -9,9 +9,10 @@
** May you share freely, never taking more than you give. ** May you share freely, never taking more than you give.
** **
************************************************************************* *************************************************************************
** This file houses the NotNull annotaion for the sqlite3 C API. ** This file houses the NotNull annotation for the sqlite3 C API.
*/ */
package org.sqlite.jni.annotation; package org.sqlite.jni.annotation;
import java.lang.annotation.*;
/** /**
This annotation is for flagging parameters which may not legally be This annotation is for flagging parameters which may not legally be
@ -64,7 +65,7 @@ package org.sqlite.jni.annotation;
part of the public API and client-level code must not rely on part of the public API and client-level code must not rely on
it.</p> it.</p>
*/ */
@java.lang.annotation.Documented @Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) @Target(ElementType.PARAMETER)
public @interface NotNull{} public @interface NotNull{}

View File

@ -9,9 +9,10 @@
** May you share freely, never taking more than you give. ** May you share freely, never taking more than you give.
** **
************************************************************************* *************************************************************************
** This file houses the Nullable annotaion for the sqlite3 C API. ** This file houses the Nullable annotation for the sqlite3 C API.
*/ */
package org.sqlite.jni.annotation; package org.sqlite.jni.annotation;
import java.lang.annotation.*;
/** /**
This annotation is for flagging parameters which may legally be This annotation is for flagging parameters which may legally be
@ -26,7 +27,7 @@ package org.sqlite.jni.annotation;
annotated functions. It is not part of the public API and annotated functions. It is not part of the public API and
client-level code must not rely on it. client-level code must not rely on it.
*/ */
@java.lang.annotation.Documented @Documented
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@java.lang.annotation.Target(java.lang.annotation.ElementType.PARAMETER) @Target(ElementType.PARAMETER)
public @interface Nullable{} public @interface Nullable{}

View File

@ -9,7 +9,7 @@
** May you share freely, never taking more than you give. ** May you share freely, never taking more than you give.
** **
************************************************************************* *************************************************************************
** This file declares JNI bindings for the sqlite3 C API. ** This file declares the main JNI bindings for the sqlite3 C API.
*/ */
package org.sqlite.jni.capi; package org.sqlite.jni.capi;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -69,7 +69,7 @@ import java.util.Arrays;
NUL-terminated, and conversion to a Java byte array must sometimes NUL-terminated, and conversion to a Java byte array must sometimes
be careful to add one. Functions which take a length do not require be careful to add one. Functions which take a length do not require
this so long as the length is provided. Search the CApi class this so long as the length is provided. Search the CApi class
for "\0" for many examples. for "\0" for examples.
</ul> </ul>
@ -119,16 +119,38 @@ public final class CApi {
<p>This routine returns false without side effects if the current <p>This routine returns false without side effects if the current
JNIEnv is not cached, else returns true, but this information is JNIEnv is not cached, else returns true, but this information is
primarily for testing of the JNI bindings and is not information primarily for testing of the JNI bindings and is not information
which client-level code can use to make any informed decisions. which client-level code can use to make any informed
decisions. Its return type and semantics are not considered
stable and may change at any time.
*/ */
public static native boolean sqlite3_java_uncache_thread(); public static native boolean sqlite3_java_uncache_thread();
/** /**
Returns true if this JVM has JNI-level support for direct memory Returns true if this JVM has JNI-level support for C-level direct
access using java.nio.ByteBuffer, else returns false. memory access using java.nio.ByteBuffer, else returns false.
*/ */
@Experimental
public static native boolean sqlite3_jni_supports_nio(); public static native boolean sqlite3_jni_supports_nio();
/**
For internal use only. Sets the given db's error code and
(optionally) string. If rc is 0, it defaults to SQLITE_ERROR.
On success it returns rc. On error it may return a more serious
code, such as SQLITE_NOMEM. Returns SQLITE_MISUSE if db is null.
*/
static native int sqlite3_jni_db_error(@NotNull sqlite3 db,
int rc, @Nullable String msg);
/**
Convenience overload which uses e.toString() as the error
message.
*/
static int sqlite3_jni_db_error(@NotNull sqlite3 db,
int rc, @NotNull Exception e){
return sqlite3_jni_db_error(db, rc, e.toString());
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Maintenance reminder: please keep the sqlite3_.... functions // Maintenance reminder: please keep the sqlite3_.... functions
// alphabetized. The SQLITE_... values. on the other hand, are // alphabetized. The SQLITE_... values. on the other hand, are
@ -215,7 +237,7 @@ public final class CApi {
If n is negative, SQLITE_MISUSE is returned. If n>data.length If n is negative, SQLITE_MISUSE is returned. If n>data.length
then n is silently truncated to data.length. then n is silently truncated to data.length.
*/ */
static int sqlite3_bind_blob( public static int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
){ ){
return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n); return sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, n);
@ -229,6 +251,30 @@ public final class CApi {
: sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length); : sqlite3_bind_blob(stmt.getNativePointer(), ndx, data, data.length);
} }
/**
Convenience overload which is a simple proxy for
sqlite3_bind_nio_buffer().
*/
@Experimental
public static int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data,
int begin, int n
){
return sqlite3_bind_nio_buffer(stmt, ndx, data, begin, n);
}
/**
Convenience overload which is equivalant to passing its arguments
to sqlite3_bind_nio_buffer() with the values 0 and -1 for the
final two arguments.
*/
@Experimental
public static int sqlite3_bind_blob(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data
){
return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1);
}
private static native int sqlite3_bind_double( private static native int sqlite3_bind_double(
@NotNull long ptrToStmt, int ndx, double v @NotNull long ptrToStmt, int ndx, double v
); );
@ -261,6 +307,61 @@ public final class CApi {
@NotNull long ptrToStmt, int ndx, @Nullable Object o @NotNull long ptrToStmt, int ndx, @Nullable Object o
); );
/**
Binds the contents of the given buffer object as a blob.
The byte range of the buffer may be restricted by providing a
start index and a number of bytes. beginPos may not be negative.
Negative howMany is interpretated as the remainder of the buffer
past the given start position, up to the buffer's limit() (as
opposed its capacity()).
If beginPos+howMany would extend past the limit() of the buffer
then SQLITE_ERROR is returned.
If any of the following are true, this function behaves like
sqlite3_bind_null(): the buffer is null, beginPos is at or past
the end of the buffer, howMany is 0, or the calculated slice of
the blob has a length of 0.
If ndx is out of range, it returns SQLITE_RANGE, as documented
for sqlite3_bind_blob(). If beginPos is negative or if
sqlite3_jni_supports_nio() returns false then SQLITE_MISUSE is
returned. Note that this function is bound (as it were) by the
SQLITE_LIMIT_LENGTH constraint and SQLITE_TOOBIG is returned if
the resulting slice of the buffer exceeds that limit.
This function does not modify the buffer's streaming-related
cursors.
If the buffer is modified in a separate thread while this
operation is running, results are undefined and will likely
result in corruption of the bound data or a segmentation fault.
Design note: this function should arguably take a java.nio.Buffer
instead of ByteBuffer, but it can only operate on "direct"
buffers and the only such class offered by Java is (apparently)
ByteBuffer.
@see https://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html
*/
@Experimental
public static native int sqlite3_bind_nio_buffer(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data,
int beginPos, int howMany
);
/**
Convenience overload which binds the given buffer's entire
contents, up to its limit() (as opposed to its capacity()).
*/
@Experimental
public static int sqlite3_bind_nio_buffer(
@NotNull sqlite3_stmt stmt, int ndx, @Nullable java.nio.ByteBuffer data
){
return sqlite3_bind_nio_buffer(stmt, ndx, data, 0, -1);
}
/** /**
Binds the given object at the given index. If o is null then this behaves like Binds the given object at the given index. If o is null then this behaves like
sqlite3_bind_null(). sqlite3_bind_null().
@ -465,13 +566,121 @@ public final class CApi {
}; };
private static native int sqlite3_blob_read( private static native int sqlite3_blob_read(
@NotNull long ptrToBlob, @NotNull byte[] target, int iOffset @NotNull long ptrToBlob, @NotNull byte[] target, int srcOffset
); );
/**
As per C's sqlite3_blob_read(), but writes its output to the
given byte array. Note that the final argument is the offset of
the source buffer, not the target array.
*/
public static int sqlite3_blob_read( public static int sqlite3_blob_read(
@NotNull sqlite3_blob b, @NotNull byte[] target, int iOffset @NotNull sqlite3_blob src, @NotNull byte[] target, int srcOffset
){ ){
return sqlite3_blob_read(b.getNativePointer(), target, iOffset); return sqlite3_blob_read(src.getNativePointer(), target, srcOffset);
}
/**
An internal level of indirection.
*/
@Experimental
private static native int sqlite3_blob_read_nio_buffer(
@NotNull long ptrToBlob, int srcOffset,
@NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany
);
/**
Reads howMany bytes from offset srcOffset of src into position
tgtOffset of tgt.
Returns SQLITE_MISUSE if src is null, tgt is null, or
sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if
howMany or either offset are negative. If argument validation
succeeds, it returns the result of the underlying call to
sqlite3_blob_read() (0 on success).
*/
@Experimental
public static int sqlite3_blob_read_nio_buffer(
@NotNull sqlite3_blob src, int srcOffset,
@NotNull java.nio.ByteBuffer tgt, int tgtOffset, int howMany
){
return (JNI_SUPPORTS_NIO && src!=null && tgt!=null)
? sqlite3_blob_read_nio_buffer(
src.getNativePointer(), srcOffset, tgt, tgtOffset, howMany
)
: SQLITE_MISUSE;
}
/**
Convenience overload which reads howMany bytes from position
srcOffset of src and returns the result as a new ByteBuffer.
srcOffset may not be negative. If howMany is negative, it is
treated as all bytes following srcOffset.
Returns null if sqlite3_jni_supports_nio(), any arguments are
invalid, if the number of bytes to read is 0 or is larger than
the src blob, or the underlying call to sqlite3_blob_read() fails
for any reason.
*/
@Experimental
public static java.nio.ByteBuffer sqlite3_blob_read_nio_buffer(
@NotNull sqlite3_blob src, int srcOffset, int howMany
){
if( !JNI_SUPPORTS_NIO || src==null ) return null;
else if( srcOffset<0 ) return null;
final int nB = sqlite3_blob_bytes(src);
if( srcOffset>=nB ) return null;
else if( howMany<0 ) howMany = nB - srcOffset;
if( srcOffset + howMany > nB ) return null;
final java.nio.ByteBuffer tgt =
java.nio.ByteBuffer.allocateDirect(howMany);
final int rc = sqlite3_blob_read_nio_buffer(
src.getNativePointer(), srcOffset, tgt, 0, howMany
);
return 0==rc ? tgt : null;
}
/**
Overload alias for sqlite3_blob_read_nio_buffer().
*/
@Experimental
public static int sqlite3_blob_read(
@NotNull sqlite3_blob src, int srcOffset,
@NotNull java.nio.ByteBuffer tgt,
int tgtOffset, int howMany
){
return sqlite3_blob_read_nio_buffer(
src, srcOffset, tgt, tgtOffset, howMany
);
}
/**
Convenience overload which uses 0 for both src and tgt offsets
and reads a number of bytes equal to the smaller of
sqlite3_blob_bytes(src) and tgt.limit().
On success it sets tgt.limit() to the number of bytes read. On
error, tgt.limit() is not modified.
Returns 0 on success. Returns SQLITE_MISUSE is either argument is
null or sqlite3_jni_supports_nio() returns false. Else it returns
the result of the underlying call to sqlite3_blob_read().
*/
@Experimental
public static int sqlite3_blob_read(
@NotNull sqlite3_blob src,
@NotNull java.nio.ByteBuffer tgt
){
if(!JNI_SUPPORTS_NIO || src==null || tgt==null) return SQLITE_MISUSE;
final int nSrc = sqlite3_blob_bytes(src);
final int nTgt = tgt.limit();
final int nRead = nTgt<nSrc ? nTgt : nSrc;
final int rc = sqlite3_blob_read_nio_buffer(
src.getNativePointer(), 0, tgt, 0, nRead
);
if( 0==rc && nTgt!=nRead ) tgt.limit( nRead );
return rc;
} }
private static native int sqlite3_blob_reopen( private static native int sqlite3_blob_reopen(
@ -492,6 +701,84 @@ public final class CApi {
return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset); return sqlite3_blob_write(b.getNativePointer(), bytes, iOffset);
} }
/**
An internal level of indirection.
*/
@Experimental
private static native int sqlite3_blob_write_nio_buffer(
@NotNull long ptrToBlob, int tgtOffset,
@NotNull java.nio.ByteBuffer src,
int srcOffset, int howMany
);
/**
Writes howMany bytes of memory from offset srcOffset of the src
buffer at position tgtOffset of b.
If howMany is negative then it's equivalent to the number of
bytes remaining starting at srcOffset.
Returns SQLITE_MISUSE if tgt is null or sqlite3_jni_supports_nio()
returns false.
Returns SQLITE_MISUSE if src is null or
sqlite3_jni_supports_nio() returns false. Returns SQLITE_ERROR if
either offset is negative. If argument validation succeeds, it
returns the result of the underlying call to sqlite3_blob_read().
*/
@Experimental
public static int sqlite3_blob_write_nio_buffer(
@NotNull sqlite3_blob tgt, int tgtOffset,
@NotNull java.nio.ByteBuffer src,
int srcOffset, int howMany
){
return sqlite3_blob_write_nio_buffer(
tgt.getNativePointer(), tgtOffset, src, srcOffset, howMany
);
}
/**
Overload alias for sqlite3_blob_write_nio_buffer().
*/
@Experimental
public static int sqlite3_blob_write(
@NotNull sqlite3_blob tgt, int tgtOffset,
@NotNull java.nio.ByteBuffer src,
int srcOffset, int howMany
){
return sqlite3_blob_write_nio_buffer(
tgt.getNativePointer(), tgtOffset, src, srcOffset, howMany
);
}
/**
Convenience overload which writes all of src to the given offset
of b.
*/
@Experimental
public static int sqlite3_blob_write(
@NotNull sqlite3_blob tgt, int tgtOffset,
@NotNull java.nio.ByteBuffer src
){
return sqlite3_blob_write_nio_buffer(
tgt.getNativePointer(), tgtOffset, src, 0, -1
);
}
/**
Convenience overload which writes all of src to offset 0
of tgt.
*/
@Experimental
public static int sqlite3_blob_write(
@NotNull sqlite3_blob tgt,
@NotNull java.nio.ByteBuffer src
){
return sqlite3_blob_write_nio_buffer(
tgt.getNativePointer(), 0, src, 0, -1
);
}
private static native int sqlite3_busy_handler( private static native int sqlite3_busy_handler(
@NotNull long ptrToDb, @Nullable BusyHandlerCallback handler @NotNull long ptrToDb, @Nullable BusyHandlerCallback handler
); );
@ -569,6 +856,15 @@ public final class CApi {
return sqlite3_column_count(stmt.getNativePointer()); return sqlite3_column_count(stmt.getNativePointer());
} }
private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
/**
Only available if built with SQLITE_ENABLE_COLUMN_METADATA.
*/
public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){
return sqlite3_column_database_name(stmt.getNativePointer(), ndx);
}
private static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx); private static native String sqlite3_column_decltype(@NotNull long ptrToStmt, int ndx);
public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){ public static String sqlite3_column_decltype(@NotNull sqlite3_stmt stmt, int ndx){
@ -623,14 +919,16 @@ public final class CApi {
return sqlite3_column_name(stmt.getNativePointer(), ndx); return sqlite3_column_name(stmt.getNativePointer(), ndx);
} }
private static native String sqlite3_column_database_name(@NotNull long ptrToStmt, int ndx);
/** /**
Only available if built with SQLITE_ENABLE_COLUMN_METADATA. A variant of sqlite3_column_blob() which returns the blob as a
ByteBuffer object. Returns null if its argument is null, if
sqlite3_jni_supports_nio() is false, or if sqlite3_column_blob()
would return null for the same inputs.
*/ */
public static String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx){ @Experimental
return sqlite3_column_database_name(stmt.getNativePointer(), ndx); public static native java.nio.ByteBuffer sqlite3_column_nio_buffer(
} @NotNull sqlite3_stmt stmt, int ndx
);
private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx); private static native String sqlite3_column_origin_name(@NotNull long ptrToStmt, int ndx);
@ -757,6 +1055,24 @@ public final class CApi {
return sqlite3_complete( nulTerminateUtf8(sql) ); return sqlite3_complete( nulTerminateUtf8(sql) );
} }
/**
Internal level of indirection for sqlite3_config(int).
*/
private static native int sqlite3_config__enable(int op);
/**
Internal level of indirection for sqlite3_config(ConfigLogCallback).
*/
private static native int sqlite3_config__CONFIG_LOG(
@Nullable ConfigLogCallback logger
);
/**
Internal level of indirection for sqlite3_config(ConfigSqlLogCallback).
*/
private static native int sqlite3_config__SQLLOG(
@Nullable ConfigSqlLogCallback logger
);
/** /**
<p>Works like in the C API with the exception that it only supports <p>Works like in the C API with the exception that it only supports
@ -773,12 +1089,14 @@ public final class CApi {
the rest of the library. This must not be called when any other the rest of the library. This must not be called when any other
library APIs are being called. library APIs are being called.
*/ */
public static native int sqlite3_config(int op); public static int sqlite3_config(int op){
return sqlite3_config__enable(op);
}
/** /**
If the native library was built with SQLITE_ENABLE_SQLLOG defined If the native library was built with SQLITE_ENABLE_SQLLOG defined
then this acts as a proxy for C's then this acts as a proxy for C's
sqlite3_config(SQLITE_ENABLE_SQLLOG,...). This sets or clears the sqlite3_config(SQLITE_CONFIG_SQLLOG,...). This sets or clears the
logger. If installation of a logger fails, any previous logger is logger. If installation of a logger fails, any previous logger is
retained. retained.
@ -789,13 +1107,17 @@ public final class CApi {
the rest of the library. This must not be called when any other the rest of the library. This must not be called when any other
library APIs are being called. library APIs are being called.
*/ */
public static native int sqlite3_config( @Nullable ConfigSqllogCallback logger ); public static int sqlite3_config( @Nullable ConfigSqlLogCallback logger ){
return sqlite3_config__SQLLOG(logger);
}
/** /**
The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG The sqlite3_config() overload for handling the SQLITE_CONFIG_LOG
option. option.
*/ */
public static native int sqlite3_config( @Nullable ConfigLogCallback logger ); public static int sqlite3_config( @Nullable ConfigLogCallback logger ){
return sqlite3_config__CONFIG_LOG(logger);
}
/** /**
Unlike the C API, this returns null if its argument is Unlike the C API, this returns null if its argument is
@ -1196,9 +1518,13 @@ public final class CApi {
array. It loops over the input bytes looking for array. It loops over the input bytes looking for
statements. Each one it finds is passed to p.call(), passing statements. Each one it finds is passed to p.call(), passing
ownership of it to that function. If p.call() returns 0, looping ownership of it to that function. If p.call() returns 0, looping
continues, else the loop stops. continues, else the loop stops and p.call()'s result code is
returned. If preparation of any given segment fails, looping
stops and that result code is returned.
<p>If p.call() throws, the exception is propagated. <p>If p.call() throws, the exception is converted to a db-level
error and a non-0 code is returned, in order to retain the
C-style error semantics of the API.
<p>How each statement is handled, including whether it is finalized <p>How each statement is handled, including whether it is finalized
or not, is up to the callback object. e.g. the callback might or not, is up to the callback object. e.g. the callback might
@ -1230,7 +1556,11 @@ public final class CApi {
// empty statement (whitespace/comments) // empty statement (whitespace/comments)
continue; continue;
} }
rc = p.call(stmt); try{
rc = p.call(stmt);
}catch(Exception e){
rc = sqlite3_jni_db_error( db, SQLITE_ERROR, e );
}
} }
return rc; return rc;
} }
@ -1341,6 +1671,15 @@ public final class CApi {
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
this acts as a proxy for C's sqlite3_preupdate_new(), else it this acts as a proxy for C's sqlite3_preupdate_new(), else it
returns SQLITE_MISUSE with no side effects. returns SQLITE_MISUSE with no side effects.
WARNING: client code _must not_ hold a reference to the returned
sqlite3_value object beyond the scope of the preupdate hook in
which this function is called. Doing so will leave the client
holding a stale pointer, the address of which could point to
anything at all after the pre-update hook is complete. This API
has no way to record such objects and clear/invalidate them at
the end of a pre-update hook. We "could" add infrastructure to do
so, but would require significant levels of bookkeeping.
*/ */
public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col, public static int sqlite3_preupdate_new(@NotNull sqlite3 db, int col,
@NotNull OutputPointer.sqlite3_value out){ @NotNull OutputPointer.sqlite3_value out){
@ -1364,6 +1703,9 @@ public final class CApi {
If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined,
this acts as a proxy for C's sqlite3_preupdate_old(), else it this acts as a proxy for C's sqlite3_preupdate_old(), else it
returns SQLITE_MISUSE with no side effects. returns SQLITE_MISUSE with no side effects.
WARNING: see warning in sqlite3_preupdate_new() regarding the
potential for stale sqlite3_value handles.
*/ */
public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col, public static int sqlite3_preupdate_old(@NotNull sqlite3 db, int col,
@NotNull OutputPointer.sqlite3_value out){ @NotNull OutputPointer.sqlite3_value out){
@ -1487,6 +1829,41 @@ public final class CApi {
@NotNull sqlite3_context cx, @NotNull Object o @NotNull sqlite3_context cx, @NotNull Object o
); );
/**
Similar to sqlite3_bind_nio_buffer(), this works like
sqlite3_result_blob() but accepts a java.nio.ByteBuffer as its
input source. See sqlite3_bind_nio_buffer() for the semantics of
the second and subsequent arguments.
If cx is null then this function will silently fail. If
sqlite3_jni_supports_nio() returns false or iBegin is negative,
an error result is set. If (begin+n) extends beyond the end of
the buffer, it is silently truncated to fit.
If any of the following apply, this function behaves like
sqlite3_result_null(): the blob is null, the resulting slice of
the blob is empty.
If the resulting slice of the buffer exceeds SQLITE_LIMIT_LENGTH
then this function behaves like sqlite3_result_error_toobig().
*/
@Experimental
public static native void sqlite3_result_nio_buffer(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob,
int begin, int n
);
/**
Convenience overload which uses the whole input object
as the result blob content.
*/
@Experimental
public static void sqlite3_result_nio_buffer(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob
){
sqlite3_result_nio_buffer(cx, blob, 0, -1);
}
public static native void sqlite3_result_null( public static native void sqlite3_result_null(
@NotNull sqlite3_context cx @NotNull sqlite3_context cx
); );
@ -1581,6 +1958,29 @@ public final class CApi {
sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length));
} }
/**
Convenience overload which behaves like
sqlite3_result_nio_buffer().
*/
@Experimental
public static void sqlite3_result_blob(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob,
int begin, int n
){
sqlite3_result_nio_buffer(cx, blob, begin, n);
}
/**
Convenience overload which behaves like the two-argument overload of
sqlite3_result_nio_buffer().
*/
@Experimental
public static void sqlite3_result_blob(
@NotNull sqlite3_context cx, @Nullable java.nio.ByteBuffer blob
){
sqlite3_result_nio_buffer(cx, blob);
}
/** /**
Binds the given text using C's sqlite3_result_blob64() unless: Binds the given text using C's sqlite3_result_blob64() unless:
@ -1638,7 +2038,8 @@ public final class CApi {
<ul> <ul>
<li>text is null: translates to a call to sqlite3_result_null()</li> <li>text is null: translates to a call to {@link
#sqlite3_result_null}</li>
<li>text is too large: translates to a call to <li>text is too large: translates to a call to
{@link #sqlite3_result_error_toobig}</li> {@link #sqlite3_result_error_toobig}</li>
@ -1979,6 +2380,17 @@ public final class CApi {
return type.isInstance(o) ? (T)o : null; return type.isInstance(o) ? (T)o : null;
} }
/**
A variant of sqlite3_column_blob() which returns the blob as a
ByteBuffer object. Returns null if its argument is null, if
sqlite3_jni_supports_nio() is false, or if sqlite3_value_blob()
would return null for the same input.
*/
@Experimental
public static native java.nio.ByteBuffer sqlite3_value_nio_buffer(
@NotNull sqlite3_value v
);
private static native int sqlite3_value_nochange(@NotNull long ptrToValue); private static native int sqlite3_value_nochange(@NotNull long ptrToValue);
public static int sqlite3_value_nochange(@NotNull sqlite3_value v){ public static int sqlite3_value_nochange(@NotNull sqlite3_value v){
@ -2478,8 +2890,8 @@ public final class CApi {
public static final int SQLITE_FAIL = 3; public static final int SQLITE_FAIL = 3;
public static final int SQLITE_REPLACE = 5; public static final int SQLITE_REPLACE = 5;
static { static {
// This MUST come after the SQLITE_MAX_... values or else
// attempting to modify them silently fails.
init(); init();
} }
/* Must come after static init(). */
private static final boolean JNI_SUPPORTS_NIO = sqlite3_jni_supports_nio();
} }

View File

@ -16,10 +16,10 @@ package org.sqlite.jni.capi;
/** /**
A callback for use with sqlite3_config(). A callback for use with sqlite3_config().
*/ */
public interface ConfigSqllogCallback { public interface ConfigSqlLogCallback {
/** /**
Must function as described for a C-level callback for Must function as described for a C-level callback for
{@link CApi#sqlite3_config(ConfigSqllogCallback)}, with the slight signature change. {@link CApi#sqlite3_config(ConfigSqlLogCallback)}, with the slight signature change.
*/ */
void call(sqlite3 db, String msg, int msgType ); void call(sqlite3 db, String msg, int msgType );
} }

View File

@ -228,4 +228,26 @@ public final class OutputPointer {
/** Sets the current value. */ /** Sets the current value. */
public final void set(byte[] v){value = v;} public final void set(byte[] v){value = v;}
} }
/**
Output pointer for use with native routines which return
blobs via java.nio.ByteBuffer.
See {@link org.sqlite.jni.capi.CApi#sqlite3_jni_supports_nio}
*/
public static final class ByteBuffer {
/**
This is public for ease of use. Accessors are provided for
consistency with the higher-level types.
*/
public java.nio.ByteBuffer value;
/** Initializes with the value null. */
public ByteBuffer(){this(null);}
/** Initializes with the value v. */
public ByteBuffer(java.nio.ByteBuffer v){value = v;}
/** Returns the current value. */
public final java.nio.ByteBuffer get(){return value;}
/** Sets the current value. */
public final void set(java.nio.ByteBuffer v){value = v;}
}
} }

View File

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

View File

@ -38,6 +38,14 @@ import java.util.concurrent.Future;
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface SingleThreadOnly{} @interface SingleThreadOnly{}
/**
Annotation for Tester1 tests which must only be run if
sqlite3_jni_supports_nio() is true.
*/
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
@interface RequiresJniNio{}
public class Tester1 implements Runnable { public class Tester1 implements Runnable {
//! True when running in multi-threaded mode. //! True when running in multi-threaded mode.
private static boolean mtMode = false; private static boolean mtMode = false;
@ -486,6 +494,7 @@ public class Tester1 implements Runnable {
stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
StringBuilder sbuf = new StringBuilder(); StringBuilder sbuf = new StringBuilder();
n = 0; n = 0;
final boolean tryNio = sqlite3_jni_supports_nio();
while( SQLITE_ROW == sqlite3_step(stmt) ){ while( SQLITE_ROW == sqlite3_step(stmt) ){
final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0)); final sqlite3_value sv = sqlite3_value_dup(sqlite3_column_value(stmt,0));
final String txt = sqlite3_column_text16(stmt, 0); final String txt = sqlite3_column_text16(stmt, 0);
@ -500,6 +509,15 @@ public class Tester1 implements Runnable {
StandardCharsets.UTF_8)) ); StandardCharsets.UTF_8)) );
affirm( txt.length() == sqlite3_value_bytes16(sv)/2 ); affirm( txt.length() == sqlite3_value_bytes16(sv)/2 );
affirm( txt.equals(sqlite3_value_text16(sv)) ); affirm( txt.equals(sqlite3_value_text16(sv)) );
if( tryNio ){
java.nio.ByteBuffer bu = sqlite3_value_nio_buffer(sv);
byte ba[] = sqlite3_value_blob(sv);
affirm( ba.length == bu.capacity() );
int i = 0;
for( byte b : ba ){
affirm( b == bu.get(i++) );
}
}
sqlite3_value_free(sv); sqlite3_value_free(sv);
++n; ++n;
} }
@ -557,6 +575,79 @@ public class Tester1 implements Runnable {
sqlite3_close_v2(db); sqlite3_close_v2(db);
} }
@RequiresJniNio
private void testBindByteBuffer(){
/* TODO: these tests need to be much more extensive to check the
begin/end range handling. */
java.nio.ByteBuffer zeroCheck =
java.nio.ByteBuffer.allocateDirect(0);
affirm( null != zeroCheck );
zeroCheck = null;
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
final java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocateDirect(10);
buf.put((byte)0x31)/*note that we'll skip this one*/
.put((byte)0x32)
.put((byte)0x33)
.put((byte)0x34)
.put((byte)0x35)/*we'll skip this one too*/;
final int expectTotal = buf.get(1) + buf.get(2) + buf.get(3);
sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
affirm( SQLITE_ERROR == sqlite3_bind_blob(stmt, 1, buf, -1, 0),
"Buffer offset may not be negative." );
affirm( 0 == sqlite3_bind_blob(stmt, 1, buf, 1, 3) );
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
stmt = prepare(db, "SELECT a FROM t;");
int total = 0;
affirm( SQLITE_ROW == sqlite3_step(stmt) );
byte blob[] = sqlite3_column_blob(stmt, 0);
java.nio.ByteBuffer nioBlob =
sqlite3_column_nio_buffer(stmt, 0);
affirm(3 == blob.length);
affirm(blob.length == nioBlob.capacity());
affirm(blob.length == nioBlob.limit());
int i = 0;
for(byte b : blob){
affirm( i<=3 );
affirm(b == buf.get(1 + i));
affirm(b == nioBlob.get(i));
++i;
total += b;
}
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
affirm(total == expectTotal);
SQLFunction func =
new ScalarFunction(){
public void xFunc(sqlite3_context cx, sqlite3_value[] args){
sqlite3_result_blob(cx, buf, 1, 3);
}
};
affirm( 0 == sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func) );
stmt = prepare(db, "SELECT myfunc()");
affirm( SQLITE_ROW == sqlite3_step(stmt) );
blob = sqlite3_column_blob(stmt, 0);
affirm(3 == blob.length);
i = 0;
total = 0;
for(byte b : blob){
affirm( i<=3 );
affirm(b == buf.get(1 + i++));
total += b;
}
affirm( SQLITE_DONE == sqlite3_step(stmt) );
sqlite3_finalize(stmt);
affirm(total == expectTotal);
sqlite3_close_v2(db);
}
private void testSql(){ private void testSql(){
sqlite3 db = createNewDb(); sqlite3 db = createNewDb();
sqlite3_stmt stmt = prepare(db, "SELECT 1"); sqlite3_stmt stmt = prepare(db, "SELECT 1");
@ -837,15 +928,28 @@ public class Tester1 implements Runnable {
// To confirm that xFinal() is called with no aggregate state // To confirm that xFinal() is called with no aggregate state
// when the corresponding result set is empty. // when the corresponding result set is empty.
new ValueHolder<>(false); new ValueHolder<>(false);
final ValueHolder<sqlite3_value[]> neverEverDoThisInClientCode = new ValueHolder<>(null);
final ValueHolder<sqlite3_context> neverEverDoThisInClientCode2 = new ValueHolder<>(null);
SQLFunction func = new AggregateFunction<Integer>(){ SQLFunction func = new AggregateFunction<Integer>(){
@Override @Override
public void xStep(sqlite3_context cx, sqlite3_value[] args){ public void xStep(sqlite3_context cx, sqlite3_value[] args){
if( null==neverEverDoThisInClientCode.value ){
/* !!!NEVER!!! hold a reference to an sqlite3_value or
sqlite3_context object like this in client code! They
are ONLY legal for the duration of their single
call. We do it here ONLY to test that the defenses
against clients doing this are working. */
neverEverDoThisInClientCode.value = args;
}
final ValueHolder<Integer> agg = this.getAggregateState(cx, 0); final ValueHolder<Integer> agg = this.getAggregateState(cx, 0);
agg.value += sqlite3_value_int(args[0]); agg.value += sqlite3_value_int(args[0]);
affirm( agg == this.getAggregateState(cx, 0) ); affirm( agg == this.getAggregateState(cx, 0) );
} }
@Override @Override
public void xFinal(sqlite3_context cx){ public void xFinal(sqlite3_context cx){
if( null==neverEverDoThisInClientCode2.value ){
neverEverDoThisInClientCode2.value = cx;
}
final Integer v = this.takeAggregateState(cx); final Integer v = this.takeAggregateState(cx);
if(null == v){ if(null == v){
xFinalNull.value = true; xFinalNull.value = true;
@ -870,6 +974,10 @@ public class Tester1 implements Runnable {
} }
affirm( 1==n ); affirm( 1==n );
affirm(!xFinalNull.value); affirm(!xFinalNull.value);
affirm( null!=neverEverDoThisInClientCode.value );
affirm( null!=neverEverDoThisInClientCode2.value );
affirm( 0<neverEverDoThisInClientCode.value.length );
affirm( 0==neverEverDoThisInClientCode2.value.getNativePointer() );
sqlite3_reset(stmt); sqlite3_reset(stmt);
affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) ); affirm( 1==sqlite3_stmt_status(stmt, SQLITE_STMTSTATUS_RUN, false) );
// Ensure that the accumulator is reset on subsequent calls... // Ensure that the accumulator is reset on subsequent calls...
@ -1652,6 +1760,86 @@ public class Tester1 implements Runnable {
affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
rc = sqlite3_blob_close(b); rc = sqlite3_blob_close(b);
affirm( 0==rc ); affirm( 0==rc );
if( !sqlite3_jni_supports_nio() ){
outln("WARNING: skipping tests for ByteBuffer-using sqlite3_blob APIs ",
"because this platform lacks that support.");
sqlite3_close_v2(db);
return;
}
/* Sanity checks for the java.nio.ByteBuffer-taking overloads of
sqlite3_blob_read/write(). */
execSql(db, "UPDATE t SET a=zeroblob(10)");
b = sqlite3_blob_open(db, "main", "t", "a", 1, 1);
affirm( null!=b );
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocateDirect(10);
for( byte i = 0; i < 10; ++i ){
bb.put((int)i, (byte)(48+i & 0xff));
}
rc = sqlite3_blob_write(b, 1, bb, 1, 10);
affirm( rc==SQLITE_ERROR, "b length < (srcOffset + bb length)" );
rc = sqlite3_blob_write(b, -1, bb);
affirm( rc==SQLITE_ERROR, "Target offset may not be negative" );
rc = sqlite3_blob_write(b, 0, bb, -1, -1);
affirm( rc==SQLITE_ERROR, "Source offset may not be negative" );
rc = sqlite3_blob_write(b, 1, bb, 1, 8);
affirm( rc==0 );
// b's contents: 0 49 50 51 52 53 54 55 56 0
// ascii: 0 '1' '2' '3' '4' '5' '6' '7' '8' 0
byte br[] = new byte[10];
java.nio.ByteBuffer bbr =
java.nio.ByteBuffer.allocateDirect(bb.limit());
rc = sqlite3_blob_read( b, br, 0 );
affirm( rc==0 );
rc = sqlite3_blob_read( b, bbr );
affirm( rc==0 );
java.nio.ByteBuffer bbr2 = sqlite3_blob_read_nio_buffer(b, 0, 12);
affirm( null==bbr2, "Read size is too big");
bbr2 = sqlite3_blob_read_nio_buffer(b, -1, 3);
affirm( null==bbr2, "Source offset is negative");
bbr2 = sqlite3_blob_read_nio_buffer(b, 5, 6);
affirm( null==bbr2, "Read pos+size is too big");
bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 7);
affirm( null==bbr2, "Read pos+size is too big");
bbr2 = sqlite3_blob_read_nio_buffer(b, 4, 6);
affirm( null!=bbr2 );
java.nio.ByteBuffer bbr3 =
java.nio.ByteBuffer.allocateDirect(2 * bb.limit());
java.nio.ByteBuffer bbr4 =
java.nio.ByteBuffer.allocateDirect(5);
rc = sqlite3_blob_read( b, bbr3 );
affirm( rc==0 );
rc = sqlite3_blob_read( b, bbr4 );
affirm( rc==0 );
affirm( sqlite3_blob_bytes(b)==bbr3.limit() );
affirm( 5==bbr4.limit() );
sqlite3_blob_close(b);
affirm( 0==br[0] );
affirm( 0==br[9] );
affirm( 0==bbr.get(0) );
affirm( 0==bbr.get(9) );
affirm( bbr2.limit() == 6 );
affirm( 0==bbr3.get(0) );
{
Exception ex = null;
try{ bbr3.get(11); }
catch(Exception e){ex = e;}
affirm( ex instanceof IndexOutOfBoundsException,
"bbr3.limit() was reset by read()" );
ex = null;
}
affirm( 0==bbr4.get(0) );
for( int i = 1; i < 9; ++i ){
affirm( br[i] == 48 + i );
affirm( br[i] == bbr.get(i) );
affirm( br[i] == bbr3.get(i) );
if( i>3 ){
affirm( br[i] == bbr2.get(i-4) );
}
if( i < bbr4.limit() ){
affirm( br[i] == bbr4.get(i) );
}
}
sqlite3_close_v2(db); sqlite3_close_v2(db);
} }
@ -1664,9 +1852,13 @@ public class Tester1 implements Runnable {
}; };
final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>(); final List<sqlite3_stmt> liStmt = new ArrayList<sqlite3_stmt>();
final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll(); final PrepareMultiCallback proxy = new PrepareMultiCallback.StepAll();
final ValueHolder<String> toss = new ValueHolder<>(null);
PrepareMultiCallback m = new PrepareMultiCallback() { PrepareMultiCallback m = new PrepareMultiCallback() {
@Override public int call(sqlite3_stmt st){ @Override public int call(sqlite3_stmt st){
liStmt.add(st); liStmt.add(st);
if( null!=toss.value ){
throw new RuntimeException(toss.value);
}
return proxy.call(st); return proxy.call(st);
} }
}; };
@ -1676,6 +1868,10 @@ public class Tester1 implements Runnable {
for( sqlite3_stmt st : liStmt ){ for( sqlite3_stmt st : liStmt ){
sqlite3_finalize(st); sqlite3_finalize(st);
} }
toss.value = "This is an exception.";
rc = sqlite3_prepare_multi(db, "SELECT 1", m);
affirm( SQLITE_ERROR==rc );
affirm( sqlite3_errmsg(db).indexOf(toss.value)>0 );
sqlite3_close_v2(db); sqlite3_close_v2(db);
} }
@ -1828,7 +2024,7 @@ public class Tester1 implements Runnable {
if( sqlLog ){ if( sqlLog ){
if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){ if( sqlite3_compileoption_used("ENABLE_SQLLOG") ){
final ConfigSqllogCallback log = new ConfigSqllogCallback() { final ConfigSqlLogCallback log = new ConfigSqlLogCallback() {
@Override public void call(sqlite3 db, String msg, int op){ @Override public void call(sqlite3 db, String msg, int op){
switch(op){ switch(op){
case 0: outln("Opening db: ",db); break; case 0: outln("Opening db: ",db); break;
@ -1839,7 +2035,7 @@ public class Tester1 implements Runnable {
}; };
int rc = sqlite3_config( log ); int rc = sqlite3_config( log );
affirm( 0==rc ); affirm( 0==rc );
rc = sqlite3_config( (ConfigSqllogCallback)null ); rc = sqlite3_config( (ConfigSqlLogCallback)null );
affirm( 0==rc ); affirm( 0==rc );
rc = sqlite3_config( log ); rc = sqlite3_config( log );
affirm( 0==rc ); affirm( 0==rc );
@ -1877,18 +2073,20 @@ public class Tester1 implements Runnable {
if( forceFail ){ if( forceFail ){
testMethods.add(m); testMethods.add(m);
} }
}else if( m.isAnnotationPresent( RequiresJniNio.class )
&& !sqlite3_jni_supports_nio() ){
outln("Skipping test for lack of JNI java.nio.ByteBuffer support: ",
name,"()\n");
++nSkipped;
}else if( !m.isAnnotationPresent( ManualTest.class ) ){ }else if( !m.isAnnotationPresent( ManualTest.class ) ){
if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){ if( nThread>1 && m.isAnnotationPresent( SingleThreadOnly.class ) ){
if( 0==nSkipped++ ){ out("Skipping test in multi-thread mode: ",name,"()\n");
out("Skipping tests in multi-thread mode:"); ++nSkipped;
}
out(" "+name+"()");
}else if( name.startsWith("test") ){ }else if( name.startsWith("test") ){
testMethods.add(m); testMethods.add(m);
} }
} }
} }
if( nSkipped>0 ) out("\n");
} }
final long timeStart = System.currentTimeMillis(); final long timeStart = System.currentTimeMillis();

View File

@ -9,7 +9,8 @@
** May you share freely, never taking more than you give. ** May you share freely, never taking more than you give.
** **
************************************************************************* *************************************************************************
** This file holds utility code for the sqlite3 JNI bindings. ** This file contains the ValueHolder utility class for the sqlite3
** JNI bindings.
*/ */
package org.sqlite.jni.capi; package org.sqlite.jni.capi;

View File

@ -19,6 +19,7 @@ import org.sqlite.jni.capi.sqlite3_stmt;
import org.sqlite.jni.capi.sqlite3_backup; import org.sqlite.jni.capi.sqlite3_backup;
import org.sqlite.jni.capi.sqlite3_blob; import org.sqlite.jni.capi.sqlite3_blob;
import org.sqlite.jni.capi.OutputPointer; import org.sqlite.jni.capi.OutputPointer;
import java.nio.ByteBuffer;
/** /**
This class represents a database connection, analog to the C-side This class represents a database connection, analog to the C-side
@ -29,7 +30,10 @@ import org.sqlite.jni.capi.OutputPointer;
*/ */
public final class Sqlite implements AutoCloseable { public final class Sqlite implements AutoCloseable {
private sqlite3 db; private sqlite3 db;
private static final boolean JNI_SUPPORTS_NIO =
CApi.sqlite3_jni_supports_nio();
// Result codes
public static final int OK = CApi.SQLITE_OK; public static final int OK = CApi.SQLITE_OK;
public static final int ERROR = CApi.SQLITE_ERROR; public static final int ERROR = CApi.SQLITE_ERROR;
public static final int INTERNAL = CApi.SQLITE_INTERNAL; public static final int INTERNAL = CApi.SQLITE_INTERNAL;
@ -135,14 +139,17 @@ public final class Sqlite implements AutoCloseable {
public static final int AUTH_USER = CApi.SQLITE_AUTH_USER; public static final int AUTH_USER = CApi.SQLITE_AUTH_USER;
public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY; public static final int OK_LOAD_PERMANENTLY = CApi.SQLITE_OK_LOAD_PERMANENTLY;
// sqlite3_open() flags
public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE; public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE;
public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE; public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE;
public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE; public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE;
// transaction state
public static final int TXN_NONE = CApi.SQLITE_TXN_NONE; public static final int TXN_NONE = CApi.SQLITE_TXN_NONE;
public static final int TXN_READ = CApi.SQLITE_TXN_READ; public static final int TXN_READ = CApi.SQLITE_TXN_READ;
public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE; public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE;
// sqlite3_status() ops
public static final int STATUS_MEMORY_USED = CApi.SQLITE_STATUS_MEMORY_USED; public static final int STATUS_MEMORY_USED = CApi.SQLITE_STATUS_MEMORY_USED;
public static final int STATUS_PAGECACHE_USED = CApi.SQLITE_STATUS_PAGECACHE_USED; public static final int STATUS_PAGECACHE_USED = CApi.SQLITE_STATUS_PAGECACHE_USED;
public static final int STATUS_PAGECACHE_OVERFLOW = CApi.SQLITE_STATUS_PAGECACHE_OVERFLOW; public static final int STATUS_PAGECACHE_OVERFLOW = CApi.SQLITE_STATUS_PAGECACHE_OVERFLOW;
@ -151,6 +158,7 @@ public final class Sqlite implements AutoCloseable {
public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE; public static final int STATUS_PAGECACHE_SIZE = CApi.SQLITE_STATUS_PAGECACHE_SIZE;
public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT; public static final int STATUS_MALLOC_COUNT = CApi.SQLITE_STATUS_MALLOC_COUNT;
// sqlite3_db_status() ops
public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED; public static final int DBSTATUS_LOOKASIDE_USED = CApi.SQLITE_DBSTATUS_LOOKASIDE_USED;
public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED; public static final int DBSTATUS_CACHE_USED = CApi.SQLITE_DBSTATUS_CACHE_USED;
public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED; public static final int DBSTATUS_SCHEMA_USED = CApi.SQLITE_DBSTATUS_SCHEMA_USED;
@ -165,6 +173,7 @@ public final class Sqlite implements AutoCloseable {
public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED; public static final int DBSTATUS_CACHE_USED_SHARED = CApi.SQLITE_DBSTATUS_CACHE_USED_SHARED;
public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL; public static final int DBSTATUS_CACHE_SPILL = CApi.SQLITE_DBSTATUS_CACHE_SPILL;
// Limits
public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH; public static final int LIMIT_LENGTH = CApi.SQLITE_LIMIT_LENGTH;
public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH; public static final int LIMIT_SQL_LENGTH = CApi.SQLITE_LIMIT_SQL_LENGTH;
public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN; public static final int LIMIT_COLUMN = CApi.SQLITE_LIMIT_COLUMN;
@ -178,15 +187,18 @@ public final class Sqlite implements AutoCloseable {
public static final int LIMIT_TRIGGER_DEPTH = CApi.SQLITE_LIMIT_TRIGGER_DEPTH; public static final int LIMIT_TRIGGER_DEPTH = CApi.SQLITE_LIMIT_TRIGGER_DEPTH;
public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS; public static final int LIMIT_WORKER_THREADS = CApi.SQLITE_LIMIT_WORKER_THREADS;
// sqlite3_prepare_v3() flags
public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT; public static final int PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT;
public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB; public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB;
// sqlite3_trace_v2() flags
public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT; public static final int TRACE_STMT = CApi.SQLITE_TRACE_STMT;
public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE; public static final int TRACE_PROFILE = CApi.SQLITE_TRACE_PROFILE;
public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW; public static final int TRACE_ROW = CApi.SQLITE_TRACE_ROW;
public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE; public static final int TRACE_CLOSE = CApi.SQLITE_TRACE_CLOSE;
public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE; public static final int TRACE_ALL = TRACE_STMT | TRACE_PROFILE | TRACE_ROW | TRACE_CLOSE;
// sqlite3_db_config() ops
public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY; public static final int DBCONFIG_ENABLE_FKEY = CApi.SQLITE_DBCONFIG_ENABLE_FKEY;
public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER; public static final int DBCONFIG_ENABLE_TRIGGER = CApi.SQLITE_DBCONFIG_ENABLE_TRIGGER;
public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER; public static final int DBCONFIG_ENABLE_FTS3_TOKENIZER = CApi.SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER;
@ -206,6 +218,12 @@ public final class Sqlite implements AutoCloseable {
public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS; public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS;
public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER; public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER;
// sqlite3_config() ops
public static final int CONFIG_SINGLETHREAD = CApi.SQLITE_CONFIG_SINGLETHREAD;
public static final int CONFIG_MULTITHREAD = CApi.SQLITE_CONFIG_MULTITHREAD;
public static final int CONFIG_SERIALIZED = CApi.SQLITE_CONFIG_SERIALIZED;
// Encodings
public static final int UTF8 = CApi.SQLITE_UTF8; public static final int UTF8 = CApi.SQLITE_UTF8;
public static final int UTF16 = CApi.SQLITE_UTF16; public static final int UTF16 = CApi.SQLITE_UTF16;
public static final int UTF16LE = CApi.SQLITE_UTF16LE; public static final int UTF16LE = CApi.SQLITE_UTF16LE;
@ -213,6 +231,14 @@ public final class Sqlite implements AutoCloseable {
/* We elide the UTF16_ALIGNED from this interface because it /* We elide the UTF16_ALIGNED from this interface because it
is irrelevant for the Java interface. */ is irrelevant for the Java interface. */
// SQL data type IDs
public static final int INTEGER = CApi.SQLITE_INTEGER;
public static final int FLOAT = CApi.SQLITE_FLOAT;
public static final int TEXT = CApi.SQLITE_TEXT;
public static final int BLOB = CApi.SQLITE_BLOB;
public static final int NULL = CApi.SQLITE_NULL;
// Authorizer codes.
public static final int DENY = CApi.SQLITE_DENY; public static final int DENY = CApi.SQLITE_DENY;
public static final int IGNORE = CApi.SQLITE_IGNORE; public static final int IGNORE = CApi.SQLITE_IGNORE;
public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX; public static final int CREATE_INDEX = CApi.SQLITE_CREATE_INDEX;
@ -258,6 +284,32 @@ public final class Sqlite implements AutoCloseable {
private static final java.util.Map<org.sqlite.jni.capi.sqlite3, Sqlite> nativeToWrapper private static final java.util.Map<org.sqlite.jni.capi.sqlite3, Sqlite> nativeToWrapper
= new java.util.HashMap<>(); = new java.util.HashMap<>();
/**
When any given thread is done using the SQLite library, calling
this will free up any native-side resources which may be
associated specifically with that thread. This is not strictly
necessary, in particular in applications which only use SQLite
from a single thread, but may help free some otherwise errant
resources.
Calling into SQLite from a given thread after this has been
called in that thread is harmless. The library will simply start
to re-cache certain state for that thread.
Contrariwise, failing to call this will effectively leak a small
amount of cached state for the thread, which may add up to
significant amounts if the application uses SQLite from many
threads.
This must never be called while actively using SQLite from this
thread, e.g. from within a query loop or a callback which is
operating on behalf of the library.
*/
static void uncacheThread(){
CApi.sqlite3_java_uncache_thread();
}
/** /**
Returns the Sqlite object associated with the given sqlite3 Returns the Sqlite object associated with the given sqlite3
object, or null if there is no such mapping. object, or null if there is no such mapping.
@ -339,6 +391,9 @@ public final class Sqlite implements AutoCloseable {
private static boolean hasNormalizeSql = private static boolean hasNormalizeSql =
compileOptionUsed("ENABLE_NORMALIZE"); compileOptionUsed("ENABLE_NORMALIZE");
private static boolean hasSqlLog =
compileOptionUsed("ENABLE_SQLLOG");
/** /**
Throws UnsupportedOperationException if check is false. Throws UnsupportedOperationException if check is false.
flag is expected to be the name of an SQLITE_ENABLE_... flag is expected to be the name of an SQLITE_ENABLE_...
@ -407,7 +462,7 @@ public final class Sqlite implements AutoCloseable {
new org.sqlite.jni.capi.OutputPointer.Int64(); new org.sqlite.jni.capi.OutputPointer.Int64();
org.sqlite.jni.capi.OutputPointer.Int64 pHighwater = org.sqlite.jni.capi.OutputPointer.Int64 pHighwater =
new org.sqlite.jni.capi.OutputPointer.Int64(); new org.sqlite.jni.capi.OutputPointer.Int64();
checkRc2( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) ); checkRcStatic( CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats) );
final Status s = new Status(); final Status s = new Status();
s.current = pCurrent.value; s.current = pCurrent.value;
s.peak = pHighwater.value; s.peak = pHighwater.value;
@ -486,7 +541,7 @@ public final class Sqlite implements AutoCloseable {
Like checkRc() but behaves as if that function were Like checkRc() but behaves as if that function were
called with a null db object. called with a null db object.
*/ */
private static void checkRc2(int rc){ private static void checkRcStatic(int rc){
if( 0!=rc ){ if( 0!=rc ){
if( CApi.SQLITE_NOMEM==rc ){ if( CApi.SQLITE_NOMEM==rc ){
throw new OutOfMemoryError(); throw new OutOfMemoryError();
@ -605,6 +660,14 @@ public final class Sqlite implements AutoCloseable {
prepareMulti( sql, 0, visitor ); prepareMulti( sql, 0, visitor );
} }
/**
Equivallent to prepareMulti(X,prepFlags,visitor), where X is
sql.getBytes(StandardCharsets.UTF_8).
*/
public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){
prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor);
}
/** /**
A variant of prepare() which can handle multiple SQL statements A variant of prepare() which can handle multiple SQL statements
in a single input string. For each statement in the given string, in a single input string. For each statement in the given string,
@ -619,6 +682,11 @@ public final class Sqlite implements AutoCloseable {
PrepareMultiFinalize offers a proxy which finalizes each PrepareMultiFinalize offers a proxy which finalizes each
statement after it is passed to another client-defined visitor. statement after it is passed to another client-defined visitor.
Be aware that certain legal SQL constructs may fail in the
preparation phase, before the corresponding statement can be
stepped. Most notably, authorizer checks which disallow access to
something in a statement behave that way.
*/ */
public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){ public void prepareMulti(byte sqlUtf8[], int prepFlags, PrepareMulti visitor){
int pos = 0, n = 1; int pos = 0, n = 1;
@ -646,14 +714,6 @@ public final class Sqlite implements AutoCloseable {
} }
} }
/**
Equivallent to prepareMulti(X,prepFlags,visitor), where X is
sql.getBytes(StandardCharsets.UTF_8).
*/
public void prepareMulti(String sql, int prepFlags, PrepareMulti visitor){
prepareMulti(sql.getBytes(StandardCharsets.UTF_8), prepFlags, visitor);
}
public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){ public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){
int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep, int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
new SqlFunction.ScalarAdapter(f)); new SqlFunction.ScalarAdapter(f));
@ -937,21 +997,11 @@ public final class Sqlite implements AutoCloseable {
public static final class Stmt implements AutoCloseable { public static final class Stmt implements AutoCloseable {
private Sqlite _db = null; private Sqlite _db = null;
private sqlite3_stmt stmt = null; private sqlite3_stmt stmt = null;
/**
We save the result column count in order to prevent having to
call into C to fetch that value every time we need to check
that value for the columnXyz() methods.
Design note: if this is final then we cannot zero it in
finalizeStmt().
*/
private int resultColCount;
/** Only called by the prepare() factory functions. */ /** Only called by the prepare() factory functions. */
Stmt(Sqlite db, sqlite3_stmt stmt){ Stmt(Sqlite db, sqlite3_stmt stmt){
this._db = db; this._db = db;
this.stmt = stmt; this.stmt = stmt;
this.resultColCount = CApi.sqlite3_column_count(stmt);
synchronized(nativeToWrapper){ synchronized(nativeToWrapper){
nativeToWrapper.put(this.stmt, this); nativeToWrapper.put(this.stmt, this);
} }
@ -977,7 +1027,7 @@ public final class Sqlite implements AutoCloseable {
/** /**
If this statement is still opened, its low-level handle is If this statement is still opened, its low-level handle is
returned, eelse an IllegalArgumentException is thrown. returned, else an IllegalArgumentException is thrown.
*/ */
private sqlite3_stmt thisStmt(){ private sqlite3_stmt thisStmt(){
if( null==stmt || 0==stmt.getNativePointer() ){ if( null==stmt || 0==stmt.getNativePointer() ){
@ -986,10 +1036,10 @@ public final class Sqlite implements AutoCloseable {
return stmt; return stmt;
} }
/** Throws if n is out of range of this.resultColCount. Intended /** Throws if n is out of range of this statement's result column
to be used by the columnXyz() methods. */ count. Intended to be used by the columnXyz() methods. */
private sqlite3_stmt checkColIndex(int n){ private sqlite3_stmt checkColIndex(int n){
if(n<0 || n>=this.resultColCount){ if(n<0 || n>=columnCount()){
throw new IllegalArgumentException("Column index "+n+" is out of range."); throw new IllegalArgumentException("Column index "+n+" is out of range.");
} }
return thisStmt(); return thisStmt();
@ -1013,7 +1063,6 @@ public final class Sqlite implements AutoCloseable {
CApi.sqlite3_finalize(stmt); CApi.sqlite3_finalize(stmt);
stmt = null; stmt = null;
_db = null; _db = null;
resultColCount = 0;
} }
return rc; return rc;
} }
@ -1055,6 +1104,22 @@ public final class Sqlite implements AutoCloseable {
} }
} }
/**
Works like sqlite3_step(), returning the same result codes as
that function unless throwOnError is true, in which case it
will throw an SqliteException for any result codes other than
Sqlite.ROW or Sqlite.DONE.
The utility of this overload over the no-argument one is the
ability to handle BUSY and LOCKED errors more easily.
*/
public int step(boolean throwOnError){
final int rc = (null==stmt)
? Sqlite.MISUSE
: CApi.sqlite3_step(stmt);
return throwOnError ? checkRc(rc) : rc;
}
/** /**
Returns the Sqlite which prepared this statement, or null if Returns the Sqlite which prepared this statement, or null if
this statement has been finalized. this statement has been finalized.
@ -1184,8 +1249,18 @@ public final class Sqlite implements AutoCloseable {
public String columnDeclType(int ndx){ public String columnDeclType(int ndx){
return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx ); return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx );
} }
/**
Analog to sqlite3_column_count() but throws if this statement
has been finalized.
*/
public int columnCount(){ public int columnCount(){
return resultColCount; /* We cannot reliably cache the column count in a class
member because an ALTER TABLE from a separate statement
can invalidate that count and we have no way, short of
installing a COMMIT handler or the like, of knowing when
to re-read it. We cannot install such a handler without
interfering with a client's ability to do so. */
return CApi.sqlite3_column_count(thisStmt());
} }
public int columnDataCount(){ public int columnDataCount(){
return CApi.sqlite3_data_count( thisStmt() ); return CApi.sqlite3_data_count( thisStmt() );
@ -1753,6 +1828,17 @@ public final class Sqlite implements AutoCloseable {
this.b = b; this.b = b;
} }
/**
If this blob is still opened, its low-level handle is
returned, else an IllegalArgumentException is thrown.
*/
private sqlite3_blob thisBlob(){
if( null==b || 0==b.getNativePointer() ){
throw new IllegalArgumentException("This Blob has been finalized.");
}
return b;
}
/** /**
Analog to sqlite3_blob_close(). Analog to sqlite3_blob_close().
*/ */
@ -1764,32 +1850,43 @@ public final class Sqlite implements AutoCloseable {
} }
} }
/**
Throws if the JVM does not have JNI-level support for
ByteBuffer.
*/
private void checkNio(){
if( !Sqlite.JNI_SUPPORTS_NIO ){
throw new UnsupportedOperationException(
"This JVM does not support JNI access to ByteBuffer."
);
}
}
/** /**
Analog to sqlite3_blob_reopen() but throws on error. Analog to sqlite3_blob_reopen() but throws on error.
*/ */
public void reopen(long newRowId){ public void reopen(long newRowId){
db.checkRc( CApi.sqlite3_blob_reopen(b, newRowId) ); db.checkRc( CApi.sqlite3_blob_reopen(thisBlob(), newRowId) );
} }
/** /**
Analog to sqlite3_blob_write() but throws on error. Analog to sqlite3_blob_write() but throws on error.
*/ */
public void write( byte[] bytes, int atOffset ){ public void write( byte[] bytes, int atOffset ){
db.checkRc( CApi.sqlite3_blob_write(b, bytes, atOffset) ); db.checkRc( CApi.sqlite3_blob_write(thisBlob(), bytes, atOffset) );
} }
/** /**
Analog to sqlite3_blob_read() but throws on error. Analog to sqlite3_blob_read() but throws on error.
*/ */
public void read( byte[] dest, int atOffset ){ public void read( byte[] dest, int atOffset ){
db.checkRc( CApi.sqlite3_blob_read(b, dest, atOffset) ); db.checkRc( CApi.sqlite3_blob_read(thisBlob(), dest, atOffset) );
} }
/** /**
Analog to sqlite3_blob_bytes(). Analog to sqlite3_blob_bytes().
*/ */
public int bytes(){ public int bytes(){
return CApi.sqlite3_blob_bytes(b); return CApi.sqlite3_blob_bytes(thisBlob());
} }
} }
@ -1814,4 +1911,81 @@ public final class Sqlite implements AutoCloseable {
return new Blob(this, out.take()); return new Blob(this, out.take());
} }
/**
Callback for use with libConfigLog().
*/
public interface ConfigLog {
/**
Must function as described for a C-level callback for
sqlite3_config()'s SQLITE_CONFIG_LOG callback, with the slight
signature change. Any exceptions thrown from this callback are
necessarily suppressed.
*/
void call(int errCode, String msg);
}
/**
Analog to sqlite3_config() with the SQLITE_CONFIG_LOG option,
this sets or (if log is null) clears the current logger.
*/
public static void libConfigLog(ConfigLog log){
final org.sqlite.jni.capi.ConfigLogCallback l =
null==log
? null
: new org.sqlite.jni.capi.ConfigLogCallback() {
@Override public void call(int errCode, String msg){
log.call(errCode, msg);
}
};
checkRcStatic(CApi.sqlite3_config(l));
}
/**
Callback for use with libConfigSqlLog().
*/
public interface ConfigSqlLog {
/**
Must function as described for a C-level callback for
sqlite3_config()'s SQLITE_CONFIG_SQLLOG callback, with the
slight signature change. Any exceptions thrown from this
callback are necessarily suppressed.
*/
void call(Sqlite db, String msg, int msgType);
}
/**
Analog to sqlite3_config() with the SQLITE_CONFIG_SQLLOG option,
this sets or (if log is null) clears the current logger.
If SQLite is built without SQLITE_ENABLE_SQLLOG defined then this
will throw an UnsupportedOperationException.
*/
public static void libConfigSqlLog(ConfigSqlLog log){
Sqlite.checkSupported(hasNormalizeSql, "SQLITE_ENABLE_SQLLOG");
final org.sqlite.jni.capi.ConfigSqlLogCallback l =
null==log
? null
: new org.sqlite.jni.capi.ConfigSqlLogCallback() {
@Override public void call(sqlite3 db, String msg, int msgType){
try{
log.call(fromNative(db), msg, msgType);
}catch(Exception e){
/* Suppressed */
}
}
};
checkRcStatic(CApi.sqlite3_config(l));
}
/**
Analog to the C-level sqlite3_config() with one of the
SQLITE_CONFIG_... constants defined as CONFIG_... in this
class. Throws on error, including passing of an unknown option or
if a specified option is not supported by the underlying build of
the SQLite library.
*/
public static void libConfigOp( int op ){
checkRcStatic(CApi.sqlite3_config(op));
}
} }

View File

@ -12,14 +12,13 @@
** This file contains a set of tests for the sqlite3 JNI bindings. ** This file contains a set of tests for the sqlite3 JNI bindings.
*/ */
package org.sqlite.jni.wrapper1; package org.sqlite.jni.wrapper1;
//import static org.sqlite.jni.capi.CApi.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.sqlite.jni.capi.*; import org.sqlite.jni.capi.CApi;
/** /**
An annotation for Tester2 tests which we do not want to run in An annotation for Tester2 tests which we do not want to run in
@ -133,49 +132,29 @@ public class Tester2 implements Runnable {
Executes all SQL statements in the given string. If throwOnError Executes all SQL statements in the given string. If throwOnError
is true then it will throw for any prepare/step errors, else it is true then it will throw for any prepare/step errors, else it
will return the corresponding non-0 result code. will return the corresponding non-0 result code.
TODO: reimplement this in the high-level API once it has the
multi-prepare capability.
*/ */
public static int execSql(Sqlite dbw, boolean throwOnError, String sql){ public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
final sqlite3 db = dbw.nativeHandle(); final ValueHolder<Integer> rv = new ValueHolder<>(0);
OutputPointer.Int32 oTail = new OutputPointer.Int32(); final Sqlite.PrepareMulti pm = new Sqlite.PrepareMulti(){
final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); @Override public void call(Sqlite.Stmt stmt){
int pos = 0, n = 1; try{
byte[] sqlChunk = sqlUtf8; while( Sqlite.ROW == (rv.value = stmt.step(throwOnError)) ){}
int rc = 0; }
sqlite3_stmt stmt = null; finally{ stmt.finalizeStmt(); }
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); }
while(pos < sqlChunk.length){ };
if(pos > 0){ try {
sqlChunk = Arrays.copyOfRange(sqlChunk, pos, dbw.prepareMulti(sql, pm);
sqlChunk.length); }catch(SqliteException se){
} if( throwOnError ){
if( 0==sqlChunk.length ) break; throw se;
rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); }else{
if( throwOnError ) affirm(0 == rc); /* This error (likely) happened in the prepare() phase and we
else if( 0!=rc ) break; need to preempt it. */
pos = oTail.value; rv.value = se.errcode();
stmt = outStmt.take();
if( null == stmt ){
// empty statement was parsed.
continue;
}
affirm(0 != stmt.getNativePointer());
while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){
}
CApi.sqlite3_finalize(stmt);
affirm(0 == stmt.getNativePointer());
if(Sqlite.DONE!=rc){
break;
} }
} }
CApi.sqlite3_finalize(stmt); return (rv.value==Sqlite.DONE) ? 0 : rv.value;
if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0;
if( 0!=rc && throwOnError){
throw new SqliteException(db);
}
return rc;
} }
static void execSql(Sqlite db, String sql){ static void execSql(Sqlite db, String sql){
@ -187,14 +166,6 @@ public class Tester2 implements Runnable {
affirm(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER); affirm(Sqlite.libVersionNumber() == CApi.SQLITE_VERSION_NUMBER);
} }
/* Copy/paste/rename this to add new tests. */
private void _testTemplate(){
//final sqlite3 db = createNewDb();
//sqlite3_stmt stmt = prepare(db,"SELECT 1");
//sqlite3_finalize(stmt);
//sqlite3_close_v2(db);
}
private void nap() throws InterruptedException { private void nap() throws InterruptedException {
if( takeNaps ){ if( takeNaps ){
Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0); Thread.sleep(java.util.concurrent.ThreadLocalRandom.current().nextInt(3, 17), 0);
@ -274,14 +245,14 @@ public class Tester2 implements Runnable {
affirm( "17".equals(stmt.columnText16(0)) ); affirm( "17".equals(stmt.columnText16(0)) );
affirm( !stmt.step() ); affirm( !stmt.step() );
stmt.reset(); stmt.reset();
affirm( stmt.step() ); affirm( Sqlite.ROW==stmt.step(false) );
affirm( !stmt.step() ); affirm( !stmt.step() );
affirm( 0 == stmt.finalizeStmt() ); affirm( 0 == stmt.finalizeStmt() );
affirm( null==stmt.nativeHandle() ); affirm( null==stmt.nativeHandle() );
stmt = db.prepare("SELECT ?"); stmt = db.prepare("SELECT ?");
stmt.bindObject(1, db); stmt.bindObject(1, db);
affirm( stmt.step() ); affirm( Sqlite.ROW == stmt.step(false) );
affirm( db==stmt.columnObject(0) ); affirm( db==stmt.columnObject(0) );
affirm( db==stmt.columnObject(0, Sqlite.class ) ); affirm( db==stmt.columnObject(0, Sqlite.class ) );
affirm( null==stmt.columnObject(0, Sqlite.Stmt.class ) ); affirm( null==stmt.columnObject(0, Sqlite.Stmt.class ) );
@ -782,9 +753,9 @@ public class Tester2 implements Runnable {
affirm( newHook == oldHook ); affirm( newHook == oldHook );
execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;");
affirm( 5 == counter.value ); affirm( 5 == counter.value );
hookResult.value = CApi.SQLITE_ERROR; hookResult.value = Sqlite.ERROR;
int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;");
affirm( CApi.SQLITE_CONSTRAINT_COMMITHOOK == rc ); affirm( Sqlite.CONSTRAINT_COMMITHOOK == rc );
affirm( 6 == counter.value ); affirm( 6 == counter.value );
db.close(); db.close();
} }
@ -930,10 +901,21 @@ public class Tester2 implements Runnable {
stmt.finalizeStmt(); stmt.finalizeStmt();
b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false); b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), false);
b.reopen(2);
final byte[] tgt = new byte[3]; final byte[] tgt = new byte[3];
b.read( tgt, 0 ); b.read( tgt, 0 );
affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" ); affirm( 100==tgt[0] && 101==tgt[1] && 102==tgt[2], "DEF" );
execSql(db,"UPDATE t SET a=zeroblob(10) WHERE rowid=2");
b.close();
b = db.blobOpen("main", "t", "a", db.lastInsertRowId(), true);
byte[] bw = new byte[]{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
b.write(bw, 0);
byte[] br = new byte[10];
b.read(br, 0);
for( int i = 0; i < br.length; ++i ){
affirm(bw[i] == br[i]);
}
b.close(); b.close();
db.close(); db.close();
} }
@ -968,6 +950,15 @@ public class Tester2 implements Runnable {
affirm( 9 == fCount.value ); affirm( 9 == fCount.value );
} }
/* Copy/paste/rename this to add new tests. */
private void _testTemplate(){
try (Sqlite db = openDb()) {
Sqlite.Stmt stmt = db.prepare("SELECT 1");
stmt.finalizeStmt();
}
}
private void runTests(boolean fromThread) throws Exception { private void runTests(boolean fromThread) throws Exception {
List<java.lang.reflect.Method> mlist = testMethods; List<java.lang.reflect.Method> mlist = testMethods;
affirm( null!=mlist ); affirm( null!=mlist );
@ -1014,8 +1005,7 @@ public class Tester2 implements Runnable {
listErrors.add(e); listErrors.add(e);
} }
}finally{ }finally{
affirm( CApi.sqlite3_java_uncache_thread() ); Sqlite.uncacheThread();
affirm( !CApi.sqlite3_java_uncache_thread() );
} }
} }
@ -1088,38 +1078,28 @@ public class Tester2 implements Runnable {
if( sqlLog ){ if( sqlLog ){
if( Sqlite.compileOptionUsed("ENABLE_SQLLOG") ){ if( Sqlite.compileOptionUsed("ENABLE_SQLLOG") ){
final ConfigSqllogCallback log = new ConfigSqllogCallback() { Sqlite.libConfigSqlLog( new Sqlite.ConfigSqlLog() {
@Override public void call(sqlite3 db, String msg, int op){ @Override public void call(Sqlite db, String msg, int op){
switch(op){ switch(op){
case 0: outln("Opening db: ",db); break; case 0: outln("Opening db: ",db); break;
case 1: outln("SQL ",db,": ",msg); break; case 1: outln("SQL ",db,": ",msg); break;
case 2: outln("Closing db: ",db); break; case 2: outln("Closing db: ",db); break;
} }
} }
}; }
int rc = CApi.sqlite3_config( log ); );
affirm( 0==rc );
rc = CApi.sqlite3_config( (ConfigSqllogCallback)null );
affirm( 0==rc );
rc = CApi.sqlite3_config( log );
affirm( 0==rc );
}else{ }else{
outln("WARNING: -sqllog is not active because library was built ", outln("WARNING: -sqllog is not active because library was built ",
"without SQLITE_ENABLE_SQLLOG."); "without SQLITE_ENABLE_SQLLOG.");
} }
} }
if( configLog ){ if( configLog ){
final ConfigLogCallback log = new ConfigLogCallback() { Sqlite.libConfigLog( new Sqlite.ConfigLog() {
@Override public void call(int code, String msg){ @Override public void call(int code, String msg){
outln("ConfigLogCallback: ",ResultCode.getEntryForInt(code),": ", msg); outln("ConfigLog: ",Sqlite.errstr(code),": ", msg);
}; };
}; }
int rc = CApi.sqlite3_config( log ); );
affirm( 0==rc );
rc = CApi.sqlite3_config( (ConfigLogCallback)null );
affirm( 0==rc );
rc = CApi.sqlite3_config( log );
affirm( 0==rc );
} }
quietMode = squelchTestOutput; quietMode = squelchTestOutput;
@ -1152,39 +1132,16 @@ public class Tester2 implements Runnable {
} }
final long timeStart = System.currentTimeMillis(); final long timeStart = System.currentTimeMillis();
int nLoop = 0;
switch( CApi.sqlite3_threadsafe() ){ /* Sanity checking */
case 0:
affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
"Could not switch to single-thread mode." );
affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
"Could switch to multithread mode." );
affirm( CApi.SQLITE_ERROR==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
"Could not switch to serialized threading mode." );
outln("This is a single-threaded build. Not using threads.");
nThread = 1;
break;
case 1:
case 2:
affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SINGLETHREAD ),
"Could not switch to single-thread mode." );
affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_MULTITHREAD ),
"Could not switch to multithread mode." );
affirm( 0==CApi.sqlite3_config( CApi.SQLITE_CONFIG_SERIALIZED ),
"Could not switch to serialized threading mode." );
break;
default:
affirm( false, "Unhandled SQLITE_THREADSAFE value." );
}
outln("libversion_number: ", outln("libversion_number: ",
CApi.sqlite3_libversion_number(),"\n", Sqlite.libVersionNumber(),"\n",
CApi.sqlite3_libversion(),"\n",CApi.SQLITE_SOURCE_ID,"\n", Sqlite.libVersion(),"\n",Sqlite.libSourceId(),"\n",
"SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe()); "SQLITE_THREADSAFE=",CApi.sqlite3_threadsafe());
final boolean showLoopCount = (nRepeat>1 && nThread>1); final boolean showLoopCount = (nRepeat>1 && nThread>1);
if( showLoopCount ){ if( showLoopCount ){
outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each."); outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
} }
if( takeNaps ) outln("Napping between tests is enabled."); if( takeNaps ) outln("Napping between tests is enabled.");
int nLoop = 0;
for( int n = 0; n < nRepeat; ++n ){ for( int n = 0; n < nRepeat; ++n ){
++nLoop; ++nLoop;
if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop); if( showLoopCount ) out((1==nLoop ? "" : " ")+nLoop);
@ -1226,7 +1183,7 @@ public class Tester2 implements Runnable {
if( doSomethingForDev ){ if( doSomethingForDev ){
CApi.sqlite3_jni_internal_details(); CApi.sqlite3_jni_internal_details();
} }
affirm( 0==CApi.sqlite3_release_memory(1) ); affirm( 0==Sqlite.libReleaseMemory(1) );
CApi.sqlite3_shutdown(); CApi.sqlite3_shutdown();
int nMethods = 0; int nMethods = 0;
int nNatives = 0; int nNatives = 0;

View File

@ -9,13 +9,13 @@
** May you share freely, never taking more than you give. ** May you share freely, never taking more than you give.
** **
************************************************************************* *************************************************************************
** This file contains a set of tests for the sqlite3 JNI bindings. ** This file contains the ValueHolder utility class.
*/ */
package org.sqlite.jni.wrapper1; package org.sqlite.jni.wrapper1;
/** /**
A helper class which simply holds a single value. Its primary use A helper class which simply holds a single value. Its primary use
is for communicating values out of anonymous classes, as doing so is for communicating values out of anonymous callbacks, as doing so
requires a "final" reference. requires a "final" reference.
*/ */
public class ValueHolder<T> { public class ValueHolder<T> {

View File

@ -22,7 +22,7 @@ const tryOpfsVfs = async function(sqlite3){
const opfs = sqlite3.opfs; const opfs = sqlite3.opfs;
log("tryOpfsVfs()"); log("tryOpfsVfs()");
if(!sqlite3.opfs){ if(!sqlite3.opfs){
const e = toss("OPFS is not available."); const e = new Error("OPFS is not available.");
error(e); error(e);
throw e; throw e;
} }

View File

@ -1,5 +1,5 @@
C Merge\srecent\strunk\senhancements\sand\sfixes\sinto\sthe\sjsonb\sbranch. C Merge\sall\sthe\slatest\senhancements\sand\sfixes\sfrom\strunk\sinto\sthe\sjsonb\sbranch.
D 2023-11-10T18:59:23.439 D 2023-11-15T13:23:40.944
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -238,29 +238,30 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9 F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
F ext/jni/GNUmakefile f2f3a31923293659b95225e932a286af1f2287d75bf88ad6c0fd1b9d9cd020d4 F ext/jni/GNUmakefile 59eb05f2a363bdfac8d15d66bed624bfe1ff289229184f3861b95f98a19cf4b2
F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4 F ext/jni/README.md d899789a9082a07b99bf30b1bbb6204ae57c060efcaa634536fa669323918f42
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
F ext/jni/src/c/sqlite3-jni.c 3774703e5865e7ff776b762de5386af8aa703e569bbb3a85c423c3f8473a3c26 F ext/jni/src/c/sqlite3-jni.c 6040c0de97644a1fb14bb589ee9f2f4208f6e6b165d14a0e33ed24945b118838
F ext/jni/src/c/sqlite3-jni.h 891444578550a7aa69fe5e0dedb3e6dedad752501ba99801f17797be51796934 F ext/jni/src/c/sqlite3-jni.h 913ab8e8fee432ae40f0e387c8231118d17053714703f5ded18202912a8a3fbf
F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 02091a8112e33389f1c160f506cd413168c8dfacbeda608a4946c6e3557b7d5a F ext/jni/src/org/sqlite/jni/annotation/Experimental.java 8603498634e41d0f7c70f661f64e05df64376562ea8f126829fd1e0cdd47e82b
F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba F ext/jni/src/org/sqlite/jni/annotation/NotNull.java 38e7e58a69b26dc100e458b31dfa3b2a7d67bc36d051325526ef1987d5bc8a24
F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 56e3dee1f3f703a545dfdeddc1c3d64d1581172b1ad01ffcae95c18547fafd90
F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca F ext/jni/src/org/sqlite/jni/annotation/package-info.java 977b374aed9d5853cbf3438ba3b0940abfa2ea4574f702a2448ee143b98ac3ca
F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c F ext/jni/src/org/sqlite/jni/capi/AbstractCollationCallback.java 1afa90d3f236f79cc7fcd2497e111992644f7596fbc8e8bcf7f1908ae00acd6c
F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63418129656daa9a9f30e7e7be982bd5ab394b1dbd0 F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63418129656daa9a9f30e7e7be982bd5ab394b1dbd0
F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java c045a5b47e02bb5f1af91973814a905f12048c428a3504fbc5266d1c1be3de5a
F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759 F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759
F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca
F ext/jni/src/org/sqlite/jni/capi/CApi.java 92d443b08175c798e132a312f71b1a42140c60d473d35c149e3d95a45b6550f3 F ext/jni/src/org/sqlite/jni/capi/CApi.java d428a1fd3b827f01c55d10d21ff35e33e7dac9e8a1d92a8b5c7d7255e67407d8
F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b
F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java 5bfa226a8e7a92e804fd52d6e42b4c7b875fa7a94f8e2c330af8cc244a8920ab
F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 482f53dfec9e3ac2a9070d3fceebd56250932aaaf7c4f5bc8de29fc011416e0c F ext/jni/src/org/sqlite/jni/capi/CommitHookCallback.java 482f53dfec9e3ac2a9070d3fceebd56250932aaaf7c4f5bc8de29fc011416e0c
F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4 F ext/jni/src/org/sqlite/jni/capi/ConfigLogCallback.java b995ca412f59b631803b93aa5b3684fce62e335d1e123207084c054abfd488d4
F ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java 701f2e4d8bdeb27cfbeeb56315d15b13d8752b0fdbca705f31bd4366c58d8a33 F ext/jni/src/org/sqlite/jni/capi/ConfigSqlLogCallback.java e5723900b6458bc6288f52187090a78ebe0a20f403ac7c887ec9061dfe51aba7 w ext/jni/src/org/sqlite/jni/capi/ConfigSqllogCallback.java
F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61 F ext/jni/src/org/sqlite/jni/capi/NativePointerHolder.java b7036dcb1ef1b39f1f36ac605dde0ff1a24a9a01ade6aa1a605039443e089a61
F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 68f60aec7aeb5cd4e5fb83449037f668c63cb99f682ee1036cc226d0cbd909b9 F ext/jni/src/org/sqlite/jni/capi/OutputPointer.java 246b0e66c4603f41c567105a21189d138aaf8c58203ecd4928802333da553e7c
F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java aca8f9fa72e3b6602bc9a7dd3ae9f5b2808103fbbee9b2749dc96c19cdc261a1 F ext/jni/src/org/sqlite/jni/capi/PrepareMultiCallback.java 97352091abd7556167f4799076396279a51749fdae2b72a6ba61cd39b3df0359
F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java efcf57545c5e282d1dd332fa63329b3b218d98f356ef107a9dbe3979be82213a F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java efcf57545c5e282d1dd332fa63329b3b218d98f356ef107a9dbe3979be82213a
F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b F ext/jni/src/org/sqlite/jni/capi/ProgressHandlerCallback.java 01bc0c238eed2d5f93c73522cb7849a445cc9098c2ed1e78248fa20ed1cfde5b
F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc2537a25ad1628f3638398d8a60cacefa7f
@ -269,10 +270,10 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385
F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1
F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615 F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615
F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f
F ext/jni/src/org/sqlite/jni/capi/Tester1.java b1a0c015d92a8d0c07a8f6751e9b057557cec9d803e002d48ee5f3b9963abd55 F ext/jni/src/org/sqlite/jni/capi/Tester1.java e5fa17301b7266c1cbe4bcce67788e08e45871c7c72c153d515abb37e501de0a
F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723 F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56 F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java c8bdf7848e6599115d601bcc9427ff902cb33129b9be32870ac6808e04b6ae56
F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 2ce069f3e007fdbbe1f4e507a5a407fc9679da31a0aa40985e6317ed4d5ec7b5
F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e F ext/jni/src/org/sqlite/jni/capi/WindowFunction.java caf4396f91b2567904cf94bc538a069fd62260d975bd037d15a02a890ed1ef9e
F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d F ext/jni/src/org/sqlite/jni/capi/XDestroyCallback.java f3abb8dd7381f53ebba909437090caf68200f06717b8a7d6aa96fa3e8133117d
F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e F ext/jni/src/org/sqlite/jni/capi/package-info.java 08ff986a65d2be9162442c82d28a65ce431d826f188520717c2ecb1484d0a50e
@ -296,10 +297,10 @@ F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe
F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483 F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483
F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 27b141f5914c7cb0e40e90a301d5e05b77f3bd42236834a68031b7086381fafd F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 27b141f5914c7cb0e40e90a301d5e05b77f3bd42236834a68031b7086381fafd
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 0ef62b43b1d6a9f044e106b56c9ea42bc7150b82ebeb79cff58f5be08cb9a435 F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java ada39f18e4e3e9d4868dadbc3f7bfe1c6c7fde74fb1fb2954c3f0f70120b805c
F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35 F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 982538ddb4c0719ef87dfa664cd137b09890b546029a7477810bd64d4c47ee35
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 40806dbbf8e120f115e33255d1813db13b40f0a598869e299a947a580429939b F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java ce45f2ec85facbb73690096547ed166e7be82299e3d92eaa206f82b60a6ec969
F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java a84e90c43724a69c2ecebd601bc8e5139f869b7d08cb705c77ef757dacdd0593
F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745 F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
@ -633,7 +634,7 @@ F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f129
F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f
F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2 F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
F ext/wasm/tester1.c-pp.js a92dc256738dbd1b50f142d1fd0c835294ba09b7bb6526650360e942f88cb63f F ext/wasm/tester1.c-pp.js a92dc256738dbd1b50f142d1fd0c835294ba09b7bb6526650360e942f88cb63f
@ -674,7 +675,7 @@ F src/date.c 3b8d02977d160e128469de38493b4085f7c5cf4073193459909a6af3cf6d7c91
F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782
F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43 F src/dbstat.c 3b677254d512fcafd4d0b341bf267b38b235ccfddbef24f9154e19360fa22e43
F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500 F src/delete.c cb766727c78e715f9fb7ec8a7d03658ed2a3016343ca687acfcec9083cdca500
F src/expr.c 433f12e1237524482b0b2681c07da3cd54ddada2a625237cecde419f3e3a2553 F src/expr.c 88629faed0b576b7ffa3d82ce44cbcee4ed476a2bf1ea4e1d6bf1260e03b19cb
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00 F src/fkey.c a47610f0a5c6cb0ad79f8fcef039c01833dec0c751bb695f28dc0ec6a4c3ba00
F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43 F src/func.c 472f6dcfa39cf54f89a6aec76c79c225fb880a6c14469c15d361331662b9bf43
@ -724,7 +725,7 @@ F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c d017bad7ba8e778617701a0e986fdeb393d67d6afa84fb28ef4e8b8ad2acf916 F src/resolve.c d017bad7ba8e778617701a0e986fdeb393d67d6afa84fb28ef4e8b8ad2acf916
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
F src/select.c 47797c57c5ee2ad183b34a2e5d643ec7519366686bbe44a9a81df9fe304f28a7 F src/select.c 503331aca8785254a7bf3d74ab338a99118fa297e1184a4dde33b3cdf7a9d341
F src/shell.c.in 297625a1ba6ea1c08bc2ea1b838b646cad309b62bf08df0e379355629404f140 F src/shell.c.in 297625a1ba6ea1c08bc2ea1b838b646cad309b62bf08df0e379355629404f140
F src/sqlite.h.in 4f841d3d117b830ee5ee45e8d89ceff1195f3ebb72d041ace8d116ba4c103b35 F src/sqlite.h.in 4f841d3d117b830ee5ee45e8d89ceff1195f3ebb72d041ace8d116ba4c103b35
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
@ -787,7 +788,7 @@ F src/test_window.c cdae419fdcea5bad6dcd9368c685abdad6deb59e9fc8b84b153de513d394
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5 F src/tokenize.c 23d9f4539880b40226254ad9072f4ecf12eb1902e62aea47aac29928afafcfd5
F src/treeview.c 62fafcd31eea60b718f8daf448116b7b19f90134ebc6c20777ddbb07f56a3d28 F src/treeview.c c6fc972683fd00f975d8b32a81c1f25d2fb7d4035366bf45c9f5622d3ccd70ee
F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81 F src/trigger.c 0905b96b04bb6658509f711a8207287f1315cdbc3df1a1b13ba6483c8e341c81
F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92 F src/update.c 6904814dd62a7a93bbb86d9f1419c7f134a9119582645854ab02b36b676d9f92
F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242 F src/upsert.c fa125a8d3410ce9a97b02cb50f7ae68a2476c405c76aa692d3acf6b8586e9242
@ -809,7 +810,7 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
F src/wal.c bba7db5dae3ffe2c6b9c173fc10be4b570b125e985cb5b95a6c22716213adde4 F src/wal.c bba7db5dae3ffe2c6b9c173fc10be4b570b125e985cb5b95a6c22716213adde4
F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452 F src/wal.h ba252daaa94f889f4b2c17c027e823d9be47ce39da1d3799886bbd51f0490452
F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2
F src/where.c 5b14ccd10ed4cfa3d62fa83bfa623aeda4d26dbc9f451c895a21797f0a024436 F src/where.c 1fdc69ce1333e9bd6d7d3df9fa5af1373a3f5bfdd52108d1dbc0ca85a55f777e
F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1 F src/whereInt.h 4b38c5889514e3aead3f27d0ee9a26e47c3f150efc59e2a8b4e3bc8835e4d7a1
F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1 F src/wherecode.c 5d77db30a2a3dd532492ae882de114edba2fae672622056b1c7fd61f5917a8f1
F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00
@ -819,7 +820,7 @@ F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867d
F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
F test/aggfault.test 777f269d0da5b0c2524c7ff6d99ae9a93db4f1b1839a914dd2a12e3035c29829 F test/aggfault.test 777f269d0da5b0c2524c7ff6d99ae9a93db4f1b1839a914dd2a12e3035c29829
F test/aggnested.test 7929208e173f5dbdbe8f67afbc59c07db99199d39ba5ce2d8a16be2c63600f53 F test/aggnested.test ce85a6af7d59c3109e35c5f03b2cd11da1a9b1417371e2f942102d0f0d77fd62
F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba
F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87 F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13 F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13
@ -1309,7 +1310,7 @@ F test/joinC.test 1f1a602c2127f55f136e2cbd3bf2d26546614bf8cffe5902ec1ac9c07f87f2
F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28 F test/joinD.test 2ce62e7353a0702ca5e70008faf319c1d4686aa19fba34275c6d1da0e960be28
F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b F test/joinE.test d5d182f3812771e2c0d97c9dcf5dbe4c41c8e21c82560e59358731c4a3981d6b
F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127 F test/joinF.test 53dd66158806823ea680dd7543b5406af151b5aafa5cd06a7f3231cd94938127
F test/joinH.test c4301c738b05b845f273b0d94de74e953626d809dc945352909aedb199b42e5f F test/joinH.test f69e5b53b7d887914e854b6a131efbed4ea9f5ca52bdab81788bfc3e79299f43
F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497 F test/journal1.test c7b768041b7f494471531e17abc2f4f5ebf9e5096984f43ed17c4eb80ba34497
F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4 F test/journal2.test 9dac6b4ba0ca79c3b21446bbae993a462c2397c4
F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ffa140e
@ -2140,8 +2141,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 72393b003f9f8675e4a124dddd09607b5b34ddefe56716b283c68c0982fb3d96 ac39800bb2685fa287c7d834faed75f0bc61320ef986de314392d6eadb574d30 P 091a5f058dfe2115fb9213655b34f00bcec80aebb299b571975cfe4ecd5ec206 9264955e6e47aa8fc3a6f8bed192a6c12f43de49f7fba2e0cc080e47abedde14
R 6d3d51033686bbc98309c106a216587b R cb95d20763613c633369d4c9e1ac8345
U drh U drh
Z 403b43107da0aaabcdd1c627364824ec Z c8298398287bfd474381f2a9e0abdbd8
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
091a5f058dfe2115fb9213655b34f00bcec80aebb299b571975cfe4ecd5ec206 ba91408f4c044feda003ef93784ccefb619f99ab64379ced481ee8e9e890fd41

View File

@ -6769,13 +6769,14 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){
case TK_AGG_FUNCTION: { case TK_AGG_FUNCTION: {
if( (pNC->ncFlags & NC_InAggFunc)==0 if( (pNC->ncFlags & NC_InAggFunc)==0
&& pWalker->walkerDepth==pExpr->op2 && pWalker->walkerDepth==pExpr->op2
&& pExpr->pAggInfo==0
){ ){
/* Check to see if pExpr is a duplicate of another aggregate /* Check to see if pExpr is a duplicate of another aggregate
** function that is already in the pAggInfo structure ** function that is already in the pAggInfo structure
*/ */
struct AggInfo_func *pItem = pAggInfo->aFunc; struct AggInfo_func *pItem = pAggInfo->aFunc;
for(i=0; i<pAggInfo->nFunc; i++, pItem++){ for(i=0; i<pAggInfo->nFunc; i++, pItem++){
if( pItem->pFExpr==pExpr ) break; if( NEVER(pItem->pFExpr==pExpr) ) break;
if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){
break; break;
} }

View File

@ -7416,7 +7416,7 @@ int sqlite3Select(
} }
} }
} }
for(j=pTabList->nSrc-1; j>=i; j--){ for(j=pTabList->nSrc-1; j>=0; j--){
pTabList->a[j].fg.jointype &= ~JT_LTORJ; pTabList->a[j].fg.jointype &= ~JT_LTORJ;
if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; if( pTabList->a[j].fg.jointype & JT_RIGHT ) break;
} }

View File

@ -781,7 +781,7 @@ void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){
assert( pExpr->x.pList->nExpr==2 ); assert( pExpr->x.pList->nExpr==2 );
pY = pExpr->x.pList->a[0].pExpr; pY = pExpr->x.pList->a[0].pExpr;
pZ = pExpr->x.pList->a[1].pExpr; pZ = pExpr->x.pList->a[1].pExpr;
sqlite3TreeViewLine(pView, "BETWEEN"); sqlite3TreeViewLine(pView, "BETWEEN%s", zFlgs);
sqlite3TreeViewExpr(pView, pX, 1); sqlite3TreeViewExpr(pView, pX, 1);
sqlite3TreeViewExpr(pView, pY, 1); sqlite3TreeViewExpr(pView, pY, 1);
sqlite3TreeViewExpr(pView, pZ, 0); sqlite3TreeViewExpr(pView, pZ, 0);

View File

@ -678,12 +678,22 @@ static void translateColumnToCopy(
for(; iStart<iEnd; iStart++, pOp++){ for(; iStart<iEnd; iStart++, pOp++){
if( pOp->p1!=iTabCur ) continue; if( pOp->p1!=iTabCur ) continue;
if( pOp->opcode==OP_Column ){ if( pOp->opcode==OP_Column ){
#ifdef SQLITE_DEBUG
if( pParse->db->flags & SQLITE_VdbeAddopTrace ){
printf("TRANSLATE OP_Column to OP_Copy at %d\n", iStart);
}
#endif
pOp->opcode = OP_Copy; pOp->opcode = OP_Copy;
pOp->p1 = pOp->p2 + iRegister; pOp->p1 = pOp->p2 + iRegister;
pOp->p2 = pOp->p3; pOp->p2 = pOp->p3;
pOp->p3 = 0; pOp->p3 = 0;
pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */ pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */
}else if( pOp->opcode==OP_Rowid ){ }else if( pOp->opcode==OP_Rowid ){
#ifdef SQLITE_DEBUG
if( pParse->db->flags & SQLITE_VdbeAddopTrace ){
printf("TRANSLATE OP_Rowid to OP_Sequence at %d\n", iStart);
}
#endif
pOp->opcode = OP_Sequence; pOp->opcode = OP_Sequence;
pOp->p1 = iAutoidxCur; pOp->p1 = iAutoidxCur;
#ifdef SQLITE_ALLOW_ROWID_IN_VIEW #ifdef SQLITE_ALLOW_ROWID_IN_VIEW
@ -5818,7 +5828,7 @@ static SQLITE_NOINLINE void whereAddIndexedExpr(
FuncDef *pDef; FuncDef *pDef;
sqlite3 *db = pParse->db; sqlite3 *db = pParse->db;
assert( ExprUseXList(pExpr) ); assert( ExprUseXList(pExpr) );
n = ALWAYS(pExpr->x.pList) ? pExpr->x.pList->nExpr : 0; n = pExpr->x.pList ? pExpr->x.pList->nExpr : 0;
pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0);
if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ if( pDef==0 || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){
continue; continue;

View File

@ -412,7 +412,65 @@ do_execsql_test 8.2 {
) FROM t1; ) FROM t1;
} {123} } {123}
#-------------------------------------------------------------------------
# dbsqlfuzz 04408efc51ae46897c4c122b407412045ed221b4
#
reset_db
do_execsql_test 9.1 {
WITH out(i, j, k) AS (
VALUES(1234, 5678, 9012)
)
SELECT (
SELECT (
SELECT min(abc) = ( SELECT ( SELECT 1234 fROM (SELECT abc) ) )
FROM (
SELECT sum( out.i ) + ( SELECT sum( out.i ) ) AS abc FROM (SELECT out.j)
)
)
) FROM out;
} {0}
do_execsql_test 9.2 {
CREATE TABLE t1(a);
CREATE TABLE t2(b);
INSERT INTO t1 VALUES(1), (2), (3);
INSERT INTO t2 VALUES(4), (5), (6);
SELECT (
SELECT min(y) + (SELECT x) FROM (
SELECT sum(a) AS x, b AS y FROM t2
)
)
FROM t1;
} {10}
do_execsql_test 9.3 {
SELECT (
SELECT min(y) + (SELECT (SELECT x)) FROM (
SELECT sum(a) AS x, b AS y FROM t2
)
)
FROM t1;
} {10}
do_execsql_test 9.4 {
SELECT (
SELECT (SELECT x) FROM (
SELECT sum(a) AS x, b AS y FROM t2
) GROUP BY y
)
FROM t1;
} {6}
do_execsql_test 9.5 {
SELECT (
SELECT (SELECT (SELECT x)) FROM (
SELECT sum(a) AS x, b AS y FROM t2
) GROUP BY y
)
FROM t1;
} {6}

View File

@ -290,5 +290,23 @@ do_execsql_test 11.3 {
SELECT * FROM t1 LEFT JOIN t2 RIGHT JOIN t3 ON (t2.c=10) WHERE t1.a=1 SELECT * FROM t1 LEFT JOIN t2 RIGHT JOIN t3 ON (t2.c=10) WHERE t1.a=1
} {} } {}
#-------------------------------------------------------------------------
reset_db
do_execsql_test 12.1 {
CREATE TABLE t1(a1 INT, b1 TEXT);
INSERT INTO t1 VALUES(88,'');
CREATE TABLE t2(c2 INT, d2 TEXT);
INSERT INTO t2 VALUES(88,'');
CREATE TABLE t3(e3 TEXT PRIMARY KEY);
INSERT INTO t3 VALUES('');
}
do_execsql_test 12.2 {
SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1;
}
do_execsql_test 12.3 {
SELECT * FROM t1 LEFT JOIN t2 ON true RIGHT JOIN t3 ON d2=e3 WHERE c2 BETWEEN NULL AND a1;
}
finish_test finish_test