aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/jni/GNUmakefile1
-rw-r--r--ext/jni/src/c/sqlite3-jni.c63
-rw-r--r--ext/jni/src/c/sqlite3-jni.h32
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java70
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/CApi.java53
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/SQLFunction.java67
-rw-r--r--ext/jni/src/org/sqlite/jni/capi/Tester1.java13
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java70
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java243
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java526
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java31
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java152
-rw-r--r--ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java46
-rw-r--r--ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api1
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js1
-rw-r--r--ext/wasm/api/sqlite3-api-worker1.js33
-rw-r--r--ext/wasm/tester1.c-pp.js6
-rw-r--r--manifest46
-rw-r--r--manifest.uuid2
-rw-r--r--src/vdbeapi.c10
20 files changed, 1114 insertions, 352 deletions
diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile
index 19a508047..155e4e7f6 100644
--- a/ext/jni/GNUmakefile
+++ b/ext/jni/GNUmakefile
@@ -120,6 +120,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/annotation/%,\
Sqlite.java \
SqliteException.java \
ValueHolder.java \
+ WindowFunction.java \
)
JAVA_FILES.unittest := $(patsubst %,$(dir.src.jni)/%,\
diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c
index 245ce4f9e..6d54391e1 100644
--- a/ext/jni/src/c/sqlite3-jni.c
+++ b/ext/jni/src/c/sqlite3-jni.c
@@ -91,12 +91,6 @@
#endif
/**********************************************************************/
-/* SQLITE_M... */
-#ifndef SQLITE_MAX_ALLOCATION_SIZE
-# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff
-#endif
-
-/**********************************************************************/
/* SQLITE_O... */
#ifndef SQLITE_OMIT_DEPRECATED
# define SQLITE_OMIT_DEPRECATED 1
@@ -1670,8 +1664,9 @@ static int encodingTypeIsValid(int eTextRep){
}
}
-/* For use with sqlite3_result/value_pointer() */
-static const char * const ResultJavaValuePtrStr = "org.sqlite.jni.capi.ResultJavaVal";
+/* For use with sqlite3_result_pointer(), sqlite3_value_pointer(),
+ sqlite3_bind_java_object(), and sqlite3_column_java_object(). */
+static const char * const s3jni__value_jref_key = "org.sqlite.jni.capi.ResultJavaVal";
/*
** If v is not NULL, it must be a jobject global reference. Its
@@ -2181,7 +2176,9 @@ S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)(
return S3JniCast_P2L(p);
}
-/* Central auto-extension handler. */
+/*
+** Central auto-extension runner for auto-extensions created in Java.
+*/
static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
const struct sqlite3_api_routines *ignored){
int rc = 0;
@@ -2418,7 +2415,7 @@ S3JniApi(sqlite3_bind_java_object(),jint,1bind_1java_1object)(
if(pStmt){
jobject const rv = S3JniRefGlobal(val);
if( rv ){
- rc = sqlite3_bind_pointer(pStmt, ndx, rv, ResultJavaValuePtrStr,
+ rc = sqlite3_bind_pointer(pStmt, ndx, rv, s3jni__value_jref_key,
S3Jni_jobject_finalizer);
}else if(val){
rc = SQLITE_NOMEM;
@@ -2870,6 +2867,26 @@ S3JniApi(sqlite3_column_int64(),jlong,1column_1int64)(
return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
}
+S3JniApi(sqlite3_column_java_object(),jobject,1column_1java_1object)(
+ JniArgsEnvClass, jlong jpStmt, jint ndx
+){
+ sqlite3_stmt * const stmt = S3JniLongPtr_sqlite3_stmt(jpStmt);
+ jobject rv = 0;
+ if( stmt ){
+ sqlite3 * const db = sqlite3_db_handle(stmt);
+ sqlite3_value * sv;
+ sqlite3_mutex_enter(sqlite3_db_mutex(db));
+ sv = sqlite3_column_value(stmt, (int)ndx);
+ if( sv ){
+ rv = S3JniRefLocal(
+ sqlite3_value_pointer(sv, s3jni__value_jref_key)
+ );
+ }
+ sqlite3_mutex_leave(sqlite3_db_mutex(db));
+ }
+ return rv;
+}
+
S3JniApi(sqlite3_column_text(),jbyteArray,1column_1text)(
JniArgsEnvClass, jobject jpStmt, jint ndx
){
@@ -3516,9 +3533,16 @@ S3JniApi(sqlite3_errmsg(),jstring,1errmsg)(
S3JniApi(sqlite3_errstr(),jstring,1errstr)(
JniArgsEnvClass, jint rcCode
){
- jstring const rv = (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode))
- /* We know these values to be plain ASCII, so pose no MUTF-8
- ** incompatibility */;
+ jstring rv;
+ const char * z = sqlite3_errstr((int)rcCode);
+ if( !z ){
+ /* This hypothetically cannot happen, but we'll behave like the
+ low-level library would in such a case... */
+ z = "unknown error";
+ }
+ rv = (*env)->NewStringUTF(env, z)
+ /* We know these values to be plain ASCII, so pose no MUTF-8
+ ** incompatibility */;
s3jni_oom_check( rv );
return rv;
}
@@ -4351,7 +4375,7 @@ S3JniApi(sqlite3_result_java_object(),void,1result_1java_1object)(
jobject const rjv = S3JniRefGlobal(v);
if( rjv ){
sqlite3_result_pointer(pCx, rjv,
- ResultJavaValuePtrStr, S3Jni_jobject_finalizer);
+ s3jni__value_jref_key, S3Jni_jobject_finalizer);
}else{
sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx));
}
@@ -4366,6 +4390,13 @@ S3JniApi(sqlite3_result_null(),void,1result_1null)(
sqlite3_result_null(PtrGet_sqlite3_context(jpCx));
}
+S3JniApi(sqlite3_result_subtype(),void,1result_1subtype)(
+ JniArgsEnvClass, jobject jpCx, jint v
+){
+ sqlite3_result_subtype(PtrGet_sqlite3_context(jpCx), (unsigned int)v);
+}
+
+
S3JniApi(sqlite3_result_text(),void,1result_1text)(
JniArgsEnvClass, jobject jpCx, jbyteArray jBa, jint nMax
){
@@ -4594,7 +4625,7 @@ static int s3jni_strlike_glob(int isLike, JNIEnv *const env,
jbyteArray baG, jbyteArray baT, jint escLike){
int rc = 0;
jbyte * const pG = s3jni_jbyteArray_bytes(baG);
- jbyte * const pT = pG ? s3jni_jbyteArray_bytes(baT) : 0;
+ jbyte * const pT = s3jni_jbyteArray_bytes(baT);
/* Note that we're relying on the byte arrays having been
NUL-terminated on the Java side. */
@@ -4889,7 +4920,7 @@ S3JniApi(sqlite3_value_java_object(),jobject,1value_1java_1object)(
){
sqlite3_value * const sv = S3JniLongPtr_sqlite3_value(jpSVal);
return sv
- ? sqlite3_value_pointer(sv, ResultJavaValuePtrStr)
+ ? sqlite3_value_pointer(sv, s3jni__value_jref_key)
: 0;
}
diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h
index bf6df7ac9..e4393ddd8 100644
--- a/ext/jni/src/c/sqlite3-jni.h
+++ b/ext/jni/src/c/sqlite3-jni.h
@@ -1121,6 +1121,14 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1int64
/*
* Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_column_java_object
+ * Signature: (JI)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1column_1java_1object
+ (JNIEnv *, jclass, jlong, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_column_name
* Signature: (JI)Ljava/lang/String;
*/
@@ -1681,14 +1689,6 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1error_1cod
/*
* Class: org_sqlite_jni_capi_CApi
- * Method: sqlite3_result_null
- * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
- */
-JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
- (JNIEnv *, jclass, jobject);
-
-/*
- * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_result_int
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
*/
@@ -1713,6 +1713,22 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1java_1obje
/*
* Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_null
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1null
+ (JNIEnv *, jclass, jobject);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
+ * Method: sqlite3_result_subtype
+ * Signature: (Lorg/sqlite/jni/capi/sqlite3_context;I)V
+ */
+JNIEXPORT void JNICALL Java_org_sqlite_jni_capi_CApi_sqlite3_1result_1subtype
+ (JNIEnv *, jclass, jobject, jint);
+
+/*
+ * Class: org_sqlite_jni_capi_CApi
* Method: sqlite3_result_value
* Signature: (Lorg/sqlite/jni/capi/sqlite3_context;Lorg/sqlite/jni/capi/sqlite3_value;)V
*/
diff --git a/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
index 89c4f2742..1fa6c6b80 100644
--- a/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
+++ b/ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java
@@ -42,9 +42,75 @@ public abstract class AggregateFunction<T> implements SQLFunction {
*/
public void xDestroy() {}
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+ <p>T must be of a type which can be legally stored as a value in
+ java.util.HashMap<KeyType,T>.
+
+ <p>If a given aggregate or window function is called multiple times
+ in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+ then the clients need some way of knowing which call is which so
+ that they can map their state between their various UDF callbacks
+ and reset it via xFinal(). This class takes care of such
+ mappings.
+
+ <p>This class works by mapping
+ sqlite3_context.getAggregateContext() to a single piece of
+ state, of a client-defined type (the T part of this class), which
+ persists across a "matching set" of the UDF's callbacks.
+
+ <p>This class is a helper providing commonly-needed functionality
+ - it is not required for use with aggregate or window functions.
+ Client UDFs are free to perform such mappings using custom
+ approaches. The provided {@link AggregateFunction} and {@link
+ WindowFunction} classes use this.
+ */
+ public static final class PerContextState<T> {
+ private final java.util.Map<Long,ValueHolder<T>> map
+ = new java.util.HashMap<>();
+
+ /**
+ Should be called from a UDF's xStep(), xValue(), and xInverse()
+ methods, passing it that method's first argument and an initial
+ value for the persistent state. If there is currently no
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder<T>
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ <p>The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
+ final Long key = cx.getAggregateContext(true);
+ ValueHolder<T> rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
+ }
+ return rc;
+ }
+
+ /**
+ Should be called from a UDF's xFinal() method and passed that
+ method's first argument. This function removes the value
+ associated with cx.getAggregateContext() from the map and
+ returns it, returning null if no other UDF method has been
+ called to set up such a mapping. The latter condition will be
+ the case if a UDF is used in a statement which has no result
+ rows.
+ */
+ public T takeAggregateState(sqlite3_context cx){
+ final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
/** Per-invocation state for the UDF. */
- private final SQLFunction.PerContextState<T> map =
- new SQLFunction.PerContextState<>();
+ private final PerContextState<T> map = new PerContextState<>();
/**
To be called from the implementation's xStep() method, as well
diff --git a/ext/jni/src/org/sqlite/jni/capi/CApi.java b/ext/jni/src/org/sqlite/jni/capi/CApi.java
index 302cdb760..6eeeb29a2 100644
--- a/ext/jni/src/org/sqlite/jni/capi/CApi.java
+++ b/ext/jni/src/org/sqlite/jni/capi/CApi.java
@@ -32,15 +32,6 @@ import java.util.Arrays;
<p>The C-side part can be found in sqlite3-jni.c.
- <p>This class is package-private in order to keep Java clients from
- having direct access to the low-level C-style APIs, a design
- decision made by Java developers based on the C-style API being
- riddled with opportunities for Java developers to proverbially shoot
- themselves in the foot with. Third-party copies of this code may
- eliminate that guard by simply changing this class from
- package-private to public. Its methods which are intended to be
- exposed that way are all public.
-
<p>Only functions which materially differ from their C counterparts
are documented here, and only those material differences are
documented. The C documentation is otherwise applicable for these
@@ -595,6 +586,36 @@ public final class CApi {
@NotNull sqlite3_stmt stmt, int ndx
);
+ static native Object sqlite3_column_java_object(
+ @NotNull long ptrToStmt, int ndx
+ );
+
+ /**
+ If the given result column was bound with
+ sqlite3_bind_java_object() or sqlite3_result_java_object() then
+ that object is returned, else null is returned. This routine
+ requires locking the owning database's mutex briefly in order to
+ extract the object in a thread-safe way.
+ */
+ public static Object sqlite3_column_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx
+ ){
+ return sqlite3_column_java_object( stmt.getNativePointer(), ndx );
+ }
+
+ /**
+ If the two-parameter overload of sqlite3_column_java_object()
+ returns non-null and the returned value is an instance of T then
+ that object is returned, else null is returned.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T sqlite3_column_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class<T> type
+ ){
+ final Object o = sqlite3_column_java_object(stmt, ndx);
+ return type.isInstance(o) ? (T)o : null;
+ }
+
static native String sqlite3_column_name(@NotNull long ptrToStmt, int ndx);
public static String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx){
@@ -1432,10 +1453,6 @@ public final class CApi {
@NotNull sqlite3_context cx, int c
);
- public static native void sqlite3_result_null(
- @NotNull sqlite3_context cx
- );
-
public static native void sqlite3_result_int(
@NotNull sqlite3_context cx, int v
);
@@ -1464,6 +1481,10 @@ public final class CApi {
@NotNull sqlite3_context cx, @NotNull Object o
);
+ public static native void sqlite3_result_null(
+ @NotNull sqlite3_context cx
+ );
+
public static void sqlite3_result_set(
@NotNull sqlite3_context cx, @NotNull Boolean v
){
@@ -1524,6 +1545,10 @@ public final class CApi {
else sqlite3_result_blob(cx, blob, blob.length);
}
+ public static native void sqlite3_result_subtype(
+ @NotNull sqlite3_context cx, int val
+ );
+
public static native void sqlite3_result_value(
@NotNull sqlite3_context cx, @NotNull sqlite3_value v
);
@@ -1938,7 +1963,7 @@ public final class CApi {
given Class, else it returns null.
*/
@SuppressWarnings("unchecked")
- public static <T> T sqlite3_value_java_casted(@NotNull sqlite3_value v,
+ public static <T> T sqlite3_value_java_object(@NotNull sqlite3_value v,
@NotNull Class<T> type){
final Object o = sqlite3_value_java_object(v);
return type.isInstance(o) ? (T)o : null;
diff --git a/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
index 4806e2fc0..7ad1381a7 100644
--- a/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
+++ b/ext/jni/src/org/sqlite/jni/capi/SQLFunction.java
@@ -33,71 +33,4 @@ package org.sqlite.jni.capi;
*/
public interface SQLFunction {
- /**
- PerContextState assists aggregate and window functions in
- managing their accumulator state across calls to the UDF's
- callbacks.
-
- <p>T must be of a type which can be legally stored as a value in
- java.util.HashMap<KeyType,T>.
-
- <p>If a given aggregate or window function is called multiple times
- in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
- then the clients need some way of knowing which call is which so
- that they can map their state between their various UDF callbacks
- and reset it via xFinal(). This class takes care of such
- mappings.
-
- <p>This class works by mapping
- sqlite3_context.getAggregateContext() to a single piece of
- state, of a client-defined type (the T part of this class), which
- persists across a "matching set" of the UDF's callbacks.
-
- <p>This class is a helper providing commonly-needed functionality
- - it is not required for use with aggregate or window functions.
- Client UDFs are free to perform such mappings using custom
- approaches. The provided {@link AggregateFunction} and {@link
- WindowFunction} classes use this.
- */
- public static final class PerContextState<T> {
- private final java.util.Map<Long,ValueHolder<T>> map
- = new java.util.HashMap<>();
-
- /**
- Should be called from a UDF's xStep(), xValue(), and xInverse()
- methods, passing it that method's first argument and an initial
- value for the persistent state. If there is currently no
- mapping for the given context within the map, one is created
- using the given initial value, else the existing one is used
- and the 2nd argument is ignored. It returns a ValueHolder<T>
- which can be used to modify that state directly without
- requiring that the client update the underlying map's entry.
-
- <p>The caller is obligated to eventually call
- takeAggregateState() to clear the mapping.
- */
- public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
- final Long key = cx.getAggregateContext(true);
- ValueHolder<T> rc = null==key ? null : map.get(key);
- if( null==rc ){
- map.put(key, rc = new ValueHolder<>(initialValue));
- }
- return rc;
- }
-
- /**
- Should be called from a UDF's xFinal() method and passed that
- method's first argument. This function removes the value
- associated with cx.getAggregateContext() from the map and
- returns it, returning null if no other UDF method has been
- called to set up such a mapping. The latter condition will be
- the case if a UDF is used in a statement which has no result
- rows.
- */
- public T takeAggregateState(sqlite3_context cx){
- final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
- return null==h ? null : h.value;
- }
- }
-
}
diff --git a/ext/jni/src/org/sqlite/jni/capi/Tester1.java b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
index 6fb28e65b..465718565 100644
--- a/ext/jni/src/org/sqlite/jni/capi/Tester1.java
+++ b/ext/jni/src/org/sqlite/jni/capi/Tester1.java
@@ -803,13 +803,17 @@ public class Tester1 implements Runnable {
affirm( 0==rc );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
+ affirm( testResult.value == sqlite3_column_java_object(stmt, 0) );
+ affirm( testResult.value == sqlite3_column_java_object(stmt, 0, sqlite3.class) );
+ affirm( null == sqlite3_column_java_object(stmt, 0, sqlite3_stmt.class) );
+ affirm( null == sqlite3_column_java_object(stmt,1) );
final sqlite3_value v = sqlite3_column_value(stmt, 0);
affirm( testResult.value == sqlite3_value_java_object(v) );
- affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) );
+ affirm( testResult.value == sqlite3_value_java_object(v, sqlite3.class) );
affirm( testResult.value ==
- sqlite3_value_java_casted(v, testResult.value.getClass()) );
- affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) );
- affirm( null == sqlite3_value_java_casted(v, String.class) );
+ sqlite3_value_java_object(v, testResult.value.getClass()) );
+ affirm( testResult.value == sqlite3_value_java_object(v, Object.class) );
+ affirm( null == sqlite3_value_java_object(v, String.class) );
++n;
}
sqlite3_finalize(stmt);
@@ -925,7 +929,6 @@ public class Tester1 implements Runnable {
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
") AS sum_y "+
"FROM twin ORDER BY x;");
- affirm( 0 == rc );
int n = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
final String s = sqlite3_column_text16(stmt, 0);
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
index 173d775e6..6a38d4b53 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java
@@ -51,9 +51,75 @@ public abstract class AggregateFunction<T> implements SqlFunction {
*/
public void xDestroy() {}
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+ <p>T must be of a type which can be legally stored as a value in
+ java.util.HashMap<KeyType,T>.
+
+ <p>If a given aggregate or window function is called multiple times
+ in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
+ then the clients need some way of knowing which call is which so
+ that they can map their state between their various UDF callbacks
+ and reset it via xFinal(). This class takes care of such
+ mappings.
+
+ <p>This class works by mapping
+ sqlite3_context.getAggregateContext() to a single piece of
+ state, of a client-defined type (the T part of this class), which
+ persists across a "matching set" of the UDF's callbacks.
+
+ <p>This class is a helper providing commonly-needed functionality
+ - it is not required for use with aggregate or window functions.
+ Client UDFs are free to perform such mappings using custom
+ approaches. The provided {@link AggregateFunction} and {@link
+ WindowFunction} classes use this.
+ */
+ public static final class PerContextState<T> {
+ private final java.util.Map<Long,ValueHolder<T>> map
+ = new java.util.HashMap<>();
+
+ /**
+ Should be called from a UDF's xStep(), xValue(), and xInverse()
+ methods, passing it that method's first argument and an initial
+ value for the persistent state. If there is currently no
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder<T>
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ <p>The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
+ final Long key = args.getContext().getAggregateContext(true);
+ ValueHolder<T> rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
+ }
+ return rc;
+ }
+
+ /**
+ Should be called from a UDF's xFinal() method and passed that
+ method's first argument. This function removes the value
+ associated with with the arguments' aggregate context from the
+ map and returns it, returning null if no other UDF method has
+ been called to set up such a mapping. The latter condition will
+ be the case if a UDF is used in a statement which has no result
+ rows.
+ */
+ public T takeAggregateState(SqlFunction.Arguments args){
+ final ValueHolder<T> h = map.remove(args.getContext().getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
/** Per-invocation state for the UDF. */
- private final SqlFunction.PerContextState<T> map =
- new SqlFunction.PerContextState<>();
+ private final PerContextState<T> map = new PerContextState<>();
/**
To be called from the implementation's xStep() method, as well
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
index d6acda5aa..2d0ebfaf3 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
@@ -22,6 +22,17 @@ import org.sqlite.jni.capi.sqlite3_value;
*/
public interface SqlFunction {
+ public static final int DETERMINISTIC = CApi.SQLITE_DETERMINISTIC;
+ public static final int INNOCUOUS = CApi.SQLITE_INNOCUOUS;
+ public static final int DIRECTONLY = CApi.SQLITE_DIRECTONLY;
+ public static final int UTF8 = CApi.SQLITE_UTF8;
+ public static final int UTF16 = CApi.SQLITE_UTF16;
+ // /**
+ // For Window functions only and is not currently bound because
+ // doing so may require exposing sqlite3_value for effective use.
+ // */
+ // public static final int SUBTYPE = CApi.SQLITE_SUBTYPE;
+
/**
The Arguments type is an abstraction on top of the lower-level
UDF function argument types. It provides _most_ of the functionality
@@ -50,44 +61,6 @@ public interface SqlFunction {
}
/**
- Wrapper for a single SqlFunction argument. Primarily intended
- for use with the Arguments class's Iterable interface.
- */
- public final static class Arg {
- private final Arguments a;
- private final int ndx;
- /* Only for use by the Arguments class. */
- private Arg(Arguments a, int ndx){
- this.a = a;
- this.ndx = ndx;
- }
- /** Returns this argument's index in its parent argument list. */
- public int getIndex(){return ndx;}
- public int getInt(){return a.getInt(ndx);}
- public long getInt64(){return a.getInt64(ndx);}
- public double getDouble(){return a.getDouble(ndx);}
- public byte[] getBlob(){return a.getBlob(ndx);}
- public byte[] getText(){return a.getText(ndx);}
- public String getText16(){return a.getText16(ndx);}
- public int getBytes(){return a.getBytes(ndx);}
- public int getBytes16(){return a.getBytes16(ndx);}
- public Object getObject(){return a.getObject(ndx);}
- public <T> T getObjectCasted(Class<T> type){ return a.getObjectCasted(ndx, type); }
- public int getType(){return a.getType(ndx);}
- public Object getAuxData(){return a.getAuxData(ndx);}
- public void setAuxData(Object o){a.setAuxData(ndx, o);}
- }
-
- @Override
- public java.util.Iterator<SqlFunction.Arguments.Arg> iterator(){
- final Arg[] proxies = new Arg[args.length];
- for( int i = 0; i < args.length; ++i ){
- proxies[i] = new Arg(this, i);
- }
- return java.util.Arrays.stream(proxies).iterator();
- }
-
- /**
Returns the sqlite3_value at the given argument index or throws
an IllegalArgumentException exception if ndx is out of range.
*/
@@ -100,29 +73,30 @@ public interface SqlFunction {
return args[ndx];
}
+ //! Returns the underlying sqlite3_context for these arguments.
sqlite3_context getContext(){return cx;}
public int getArgCount(){ return args.length; }
- public int getInt(int arg){return CApi.sqlite3_value_int(valueAt(arg));}
- public long getInt64(int arg){return CApi.sqlite3_value_int64(valueAt(arg));}
- public double getDouble(int arg){return CApi.sqlite3_value_double(valueAt(arg));}
- public byte[] getBlob(int arg){return CApi.sqlite3_value_blob(valueAt(arg));}
- public byte[] getText(int arg){return CApi.sqlite3_value_text(valueAt(arg));}
- public String getText16(int arg){return CApi.sqlite3_value_text16(valueAt(arg));}
- public int getBytes(int arg){return CApi.sqlite3_value_bytes(valueAt(arg));}
- public int getBytes16(int arg){return CApi.sqlite3_value_bytes16(valueAt(arg));}
- public Object getObject(int arg){return CApi.sqlite3_value_java_object(valueAt(arg));}
- public <T> T getObjectCasted(int arg, Class<T> type){
- return CApi.sqlite3_value_java_casted(valueAt(arg), type);
+ public int getInt(int argNdx){return CApi.sqlite3_value_int(valueAt(argNdx));}
+ public long getInt64(int argNdx){return CApi.sqlite3_value_int64(valueAt(argNdx));}
+ public double getDouble(int argNdx){return CApi.sqlite3_value_double(valueAt(argNdx));}
+ public byte[] getBlob(int argNdx){return CApi.sqlite3_value_blob(valueAt(argNdx));}
+ public byte[] getText(int argNdx){return CApi.sqlite3_value_text(valueAt(argNdx));}
+ public String getText16(int argNdx){return CApi.sqlite3_value_text16(valueAt(argNdx));}
+ public int getBytes(int argNdx){return CApi.sqlite3_value_bytes(valueAt(argNdx));}
+ public int getBytes16(int argNdx){return CApi.sqlite3_value_bytes16(valueAt(argNdx));}
+ public Object getObject(int argNdx){return CApi.sqlite3_value_java_object(valueAt(argNdx));}
+ public <T> T getObject(int argNdx, Class<T> type){
+ return CApi.sqlite3_value_java_object(valueAt(argNdx), type);
}
- public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));}
- public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));}
- public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));}
- public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));}
- public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));}
- public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));}
+ public int getType(int argNdx){return CApi.sqlite3_value_type(valueAt(argNdx));}
+ public int getSubtype(int argNdx){return CApi.sqlite3_value_subtype(valueAt(argNdx));}
+ public int getNumericType(int argNdx){return CApi.sqlite3_value_numeric_type(valueAt(argNdx));}
+ public int getNoChange(int argNdx){return CApi.sqlite3_value_nochange(valueAt(argNdx));}
+ public boolean getFromBind(int argNdx){return CApi.sqlite3_value_frombind(valueAt(argNdx));}
+ public int getEncoding(int argNdx){return CApi.sqlite3_value_encoding(valueAt(argNdx));}
public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
@@ -134,6 +108,7 @@ public interface SqlFunction {
public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
public void resultNull(){CApi.sqlite3_result_null(cx);}
public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
+ public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);}
public void resultZeroBlob(long n){
// Throw on error? If n is too big,
// sqlite3_result_error_toobig() is automatically called.
@@ -146,88 +121,60 @@ public interface SqlFunction {
public void resultText16(byte[] utf16){CApi.sqlite3_result_text16(cx, utf16);}
public void resultText16(String txt){CApi.sqlite3_result_text16(cx, txt);}
- public void setAuxData(int arg, Object o){
+ public void setAuxData(int argNdx, Object o){
/* From the API docs: https://www.sqlite.org/c3ref/get_auxdata.html
The value of the N parameter to these interfaces should be
non-negative. Future enhancements may make use of negative N
values to define new kinds of function caching behavior.
*/
- valueAt(arg);
- CApi.sqlite3_set_auxdata(cx, arg, o);
+ valueAt(argNdx);
+ CApi.sqlite3_set_auxdata(cx, argNdx, o);
}
- public Object getAuxData(int arg){
- valueAt(arg);
- return CApi.sqlite3_get_auxdata(cx, arg);
+ public Object getAuxData(int argNdx){
+ valueAt(argNdx);
+ return CApi.sqlite3_get_auxdata(cx, argNdx);
}
- }
-
- /**
- PerContextState assists aggregate and window functions in
- managing their accumulator state across calls to the UDF's
- callbacks.
-
- <p>T must be of a type which can be legally stored as a value in
- java.util.HashMap<KeyType,T>.
-
- <p>If a given aggregate or window function is called multiple times
- in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)...,
- then the clients need some way of knowing which call is which so
- that they can map their state between their various UDF callbacks
- and reset it via xFinal(). This class takes care of such
- mappings.
-
- <p>This class works by mapping
- sqlite3_context.getAggregateContext() to a single piece of
- state, of a client-defined type (the T part of this class), which
- persists across a "matching set" of the UDF's callbacks.
-
- <p>This class is a helper providing commonly-needed functionality
- - it is not required for use with aggregate or window functions.
- Client UDFs are free to perform such mappings using custom
- approaches. The provided {@link AggregateFunction} and {@link
- WindowFunction} classes use this.
- */
- public static final class PerContextState<T> {
- private final java.util.Map<Long,ValueHolder<T>> map
- = new java.util.HashMap<>();
/**
- Should be called from a UDF's xStep(), xValue(), and xInverse()
- methods, passing it that method's first argument and an initial
- value for the persistent state. If there is currently no
- mapping for the given context within the map, one is created
- using the given initial value, else the existing one is used
- and the 2nd argument is ignored. It returns a ValueHolder<T>
- which can be used to modify that state directly without
- requiring that the client update the underlying map's entry.
-
- <p>The caller is obligated to eventually call
- takeAggregateState() to clear the mapping.
+ Wrapper for a single SqlFunction argument. Primarily intended
+ for use with the Arguments class's Iterable interface.
*/
- public ValueHolder<T> getAggregateState(SqlFunction.Arguments args, T initialValue){
- final Long key = args.getContext().getAggregateContext(true);
- ValueHolder<T> rc = null==key ? null : map.get(key);
- if( null==rc ){
- map.put(key, rc = new ValueHolder<>(initialValue));
+ public final static class Arg {
+ private final Arguments a;
+ private final int ndx;
+ /* Only for use by the Arguments class. */
+ private Arg(Arguments a, int ndx){
+ this.a = a;
+ this.ndx = ndx;
}
- return rc;
+ /** Returns this argument's index in its parent argument list. */
+ public int getIndex(){return ndx;}
+ public int getInt(){return a.getInt(ndx);}
+ public long getInt64(){return a.getInt64(ndx);}
+ public double getDouble(){return a.getDouble(ndx);}
+ public byte[] getBlob(){return a.getBlob(ndx);}
+ public byte[] getText(){return a.getText(ndx);}
+ public String getText16(){return a.getText16(ndx);}
+ public int getBytes(){return a.getBytes(ndx);}
+ public int getBytes16(){return a.getBytes16(ndx);}
+ public Object getObject(){return a.getObject(ndx);}
+ public <T> T getObject(Class<T> type){ return a.getObject(ndx, type); }
+ public int getType(){return a.getType(ndx);}
+ public Object getAuxData(){return a.getAuxData(ndx);}
+ public void setAuxData(Object o){a.setAuxData(ndx, o);}
}
- /**
- Should be called from a UDF's xFinal() method and passed that
- method's first argument. This function removes the value
- associated with with the arguments' aggregate context from the
- map and returns it, returning null if no other UDF method has
- been called to set up such a mapping. The latter condition will
- be the case if a UDF is used in a statement which has no result
- rows.
- */
- public T takeAggregateState(SqlFunction.Arguments args){
- final ValueHolder<T> h = map.remove(args.getContext().getAggregateContext(false));
- return null==h ? null : h.value;
+ @Override
+ public java.util.Iterator<SqlFunction.Arguments.Arg> iterator(){
+ final Arg[] proxies = new Arg[args.length];
+ for( int i = 0; i < args.length; ++i ){
+ proxies[i] = new Arg(this, i);
+ }
+ return java.util.Arrays.stream(proxies).iterator();
}
+
}
/**
@@ -235,7 +182,7 @@ public interface SqlFunction {
for use with the org.sqlite.jni.capi.ScalarFunction interface.
*/
static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
- final ScalarFunction impl;
+ private final ScalarFunction impl;
ScalarAdapter(ScalarFunction impl){
this.impl = impl;
}
@@ -261,8 +208,9 @@ public interface SqlFunction {
Internal-use adapter for wrapping this package's AggregateFunction
for use with the org.sqlite.jni.capi.AggregateFunction interface.
*/
- static final class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
- final AggregateFunction impl;
+ static /*cannot be final without duplicating the whole body in WindowAdapter*/
+ class AggregateAdapter extends org.sqlite.jni.capi.AggregateFunction {
+ private final AggregateFunction impl;
AggregateAdapter(AggregateFunction impl){
this.impl = impl;
}
@@ -282,8 +230,9 @@ public interface SqlFunction {
}
/**
- As for the xFinal() argument of the C API's sqlite3_create_function().
- If the proxied function throws, it is translated into a sqlite3_result_error().
+ As for the xFinal() argument of the C API's
+ sqlite3_create_function(). If the proxied function throws, it
+ is translated into a sqlite3_result_error().
*/
public void xFinal(sqlite3_context cx){
try{
@@ -298,4 +247,46 @@ public interface SqlFunction {
}
}
+ /**
+ Internal-use adapter for wrapping this package's WindowFunction
+ for use with the org.sqlite.jni.capi.WindowFunction interface.
+ */
+ static final class WindowAdapter extends AggregateAdapter {
+ private final WindowFunction impl;
+ WindowAdapter(WindowFunction impl){
+ super(impl);
+ this.impl = impl;
+ }
+
+ /**
+ Proxies this.impl.xInverse(), adapting the call arguments to that
+ function's signature. If the proxied function throws, it is
+ translated to sqlite_result_error() with the exception's
+ message.
+ */
+ public void xInverse(sqlite3_context cx, sqlite3_value[] args){
+ try{
+ impl.xInverse( new SqlFunction.Arguments(cx, args) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ /**
+ As for the xValue() argument of the C API's sqlite3_create_window_function().
+ If the proxied function throws, it is translated into a sqlite3_result_error().
+ */
+ public void xValue(sqlite3_context cx){
+ try{
+ impl.xValue( new SqlFunction.Arguments(cx, null) );
+ }catch(Exception e){
+ CApi.sqlite3_result_error(cx, e);
+ }
+ }
+
+ public void xDestroy(){
+ impl.xDestroy();
+ }
+ }
+
}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
index bcf97b239..b464bd7d5 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
@@ -29,6 +29,38 @@ import org.sqlite.jni.capi.OutputPointer;
public final class Sqlite implements AutoCloseable {
private sqlite3 db;
+ 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_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE;
+ 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_WRITE = CApi.SQLITE_TXN_WRITE;
+
+ 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_OVERFLOW = CApi.SQLITE_STATUS_PAGECACHE_OVERFLOW;
+ public static final int STATUS_MALLOC_SIZE = CApi.SQLITE_STATUS_MALLOC_SIZE;
+ public static final int STATUS_PARSER_STACK = CApi.SQLITE_STATUS_PARSER_STACK;
+ 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 LIMIT_LENGTH = CApi.SQLITE_LIMIT_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_EXPR_DEPTH = CApi.SQLITE_LIMIT_EXPR_DEPTH;
+ public static final int LIMIT_COMPOUND_SELECT = CApi.SQLITE_LIMIT_COMPOUND_SELECT;
+ public static final int LIMIT_VDBE_OP = CApi.SQLITE_LIMIT_VDBE_OP;
+ public static final int LIMIT_FUNCTION_ARG = CApi.SQLITE_LIMIT_FUNCTION_ARG;
+ public static final int LIMIT_ATTACHED = CApi.SQLITE_LIMIT_ATTACHED;
+ public static final int LIMIT_LIKE_PATTERN_LENGTH = CApi.SQLITE_LIMIT_LIKE_PATTERN_LENGTH;
+ public static final int LIMIT_VARIABLE_NUMBER = CApi.SQLITE_LIMIT_VARIABLE_NUMBER;
+ 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 PREPARE_PERSISTENT = CApi.SQLITE_PREPARE_PERSISTENT;
+ public static final int PREPARE_NORMALIZE = CApi.SQLITE_PREPARE_NORMALIZE;
+ public static final int PREPARE_NO_VTAB = CApi.SQLITE_PREPARE_NO_VTAB;
+
//! Used only by the open() factory functions.
private Sqlite(sqlite3 db){
this.db = db;
@@ -63,6 +95,33 @@ public final class Sqlite implements AutoCloseable {
return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
}
+ public static String libVersion(){
+ return CApi.sqlite3_libversion();
+ }
+
+ public static int libVersionNumber(){
+ return CApi.sqlite3_libversion_number();
+ }
+
+ public static String libSourceId(){
+ return CApi.sqlite3_sourceid();
+ }
+
+ /**
+ As per sqlite3_status64(), but returns its current and high-water
+ results as a two-element array. Throws if the first argument is
+ not one of the STATUS_... constants.
+ */
+ public long[] libStatus(int op, boolean resetStats){
+ org.sqlite.jni.capi.OutputPointer.Int64 pCurrent =
+ new org.sqlite.jni.capi.OutputPointer.Int64();
+ org.sqlite.jni.capi.OutputPointer.Int64 pHighwater =
+ new org.sqlite.jni.capi.OutputPointer.Int64();
+ final int rc = CApi.sqlite3_status64(op, pCurrent, pHighwater, resetStats);
+ checkRc(rc);
+ return new long[] {pCurrent.value, pHighwater.value};
+ }
+
@Override public void close(){
if(null!=this.db){
this.db.close();
@@ -71,13 +130,29 @@ public final class Sqlite implements AutoCloseable {
}
/**
+ Returns the value of the native library's build-time value of the
+ SQLITE_THREADSAFE build option.
+ */
+ public static int libThreadsafe(){
+ return CApi.sqlite3_threadsafe();
+ }
+
+ public static boolean strglob(String glob, String txt){
+ return 0==CApi.sqlite3_strglob(glob, txt);
+ }
+
+ public static boolean strlike(String glob, String txt, char escChar){
+ return 0==CApi.sqlite3_strlike(glob, txt, escChar);
+ }
+
+ /**
Returns this object's underlying native db handle, or null if
this instance has been closed. This is very specifically not
public.
*/
sqlite3 nativeHandle(){ return this.db; }
- private sqlite3 affirmOpen(){
+ private sqlite3 thisDb(){
if( null==db || 0==db.getNativePointer() ){
throw new IllegalArgumentException("This database instance is closed.");
}
@@ -88,10 +163,259 @@ public final class Sqlite implements AutoCloseable {
// return s==null ? null : s.getBytes(StandardCharsets.UTF_8);
// }
- private void affirmRcOk(int rc){
+ /**
+ If rc!=0, throws an SqliteException. If this db is currently
+ opened and has non-0 sqlite3_errcode(), the error state is
+ extracted from it, else only the string form of rc is used. It is
+ the caller's responsibility to filter out non-error codes such as
+ SQLITE_ROW and SQLITE_DONE before calling this.
+ */
+ private void checkRc(int rc){
if( 0!=rc ){
- throw new SqliteException(db);
+ if( null==db || 0==sqlite3_errcode(db)) throw new SqliteException(rc);
+ else throw new SqliteException(db);
+ }
+ }
+
+ /**
+ prepFlags must be 0 or a bitmask of the PREPARE_... constants.
+
+ prepare() TODOs include:
+
+ - overloads taking byte[] and ByteBuffer.
+
+ - multi-statement processing, like CApi.sqlite3_prepare_multi()
+ but using a callback specific to the higher-level Stmt class
+ rather than the sqlite3_stmt class.
+ */
+ public Stmt prepare(String sql, int prepFlags){
+ final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+ final int rc = sqlite3_prepare_v3(thisDb(), sql, prepFlags, out);
+ checkRc(rc);
+ final sqlite3_stmt q = out.take();
+ if( null==q ){
+ /* The C-level API treats input which is devoid of SQL
+ statements (e.g. all comments or an empty string) as success
+ but returns a NULL sqlite3_stmt object. In higher-level APIs,
+ wrapping a "successful NULL" object that way is tedious to
+ use because it forces clients and/or wrapper-level code to
+ check for that unusual case. In practice, higher-level
+ bindings are generally better-served by treating empty SQL
+ input as an error. */
+ throw new IllegalArgumentException("Input contains no SQL statements.");
+ }
+ return new Stmt(this, q);
+ }
+
+ public Stmt prepare(String sql){
+ return prepare(sql, 0);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f){
+ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
+ new SqlFunction.ScalarAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, ScalarFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f){
+ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
+ new SqlFunction.AggregateAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, AggregateFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public void createFunction(String name, int nArg, int eTextRep, WindowFunction f){
+ int rc = CApi.sqlite3_create_function(thisDb(), name, nArg, eTextRep,
+ new SqlFunction.WindowAdapter(f));
+ if( 0!=rc ) throw new SqliteException(db);
+ }
+
+ public void createFunction(String name, int nArg, WindowFunction f){
+ this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
+ }
+
+ public long changes(){
+ return CApi.sqlite3_changes64(thisDb());
+ }
+
+ public long totalChanges(){
+ return CApi.sqlite3_total_changes64(thisDb());
+ }
+
+ public long lastInsertRowId(){
+ return CApi.sqlite3_last_insert_rowid(thisDb());
+ }
+
+ public void setLastInsertRowId(long rowId){
+ CApi.sqlite3_set_last_insert_rowid(thisDb(), rowId);
+ }
+
+ public void interrupt(){
+ CApi.sqlite3_interrupt(thisDb());
+ }
+
+ public boolean isInterrupted(){
+ return CApi.sqlite3_is_interrupted(thisDb());
+ }
+
+ public boolean isAutoCommit(){
+ return CApi.sqlite3_get_autocommit(thisDb());
+ }
+
+ public void setBusyTimeout(int ms){
+ checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms));
+ }
+
+ /**
+ Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ,
+ or TXN_WRITE to denote this database's current transaction state
+ for the given schema name (or the most restrictive state of any
+ schema if zSchema is null).
+ */
+ public int transactionState(String zSchema){
+ return CApi.sqlite3_txn_state(thisDb(), zSchema);
+ }
+
+ /**
+ Analog to sqlite3_db_name(). Returns null if passed an unknown
+ index.
+ */
+ public String dbName(int dbNdx){
+ return CApi.sqlite3_db_name(thisDb(), dbNdx);
+ }
+
+ /**
+ Analog to sqlite3_db_filename(). Returns null if passed an
+ unknown db name.
+ */
+ public String dbFileName(String dbName){
+ return CApi.sqlite3_db_filename(thisDb(), dbName);
+ }
+
+ /**
+ Analog to the variant of sqlite3_db_config() for configuring the
+ SQLITE_DBCONFIG_MAINDBNAME option. Throws on error.
+ */
+ public void setMainDbName(String name){
+ checkRc(
+ CApi.sqlite3_db_config(thisDb(), CApi.SQLITE_DBCONFIG_MAINDBNAME,
+ name)
+ );
+ }
+
+ /**
+ Analog to sqlite3_db_readonly() but throws an SqliteException
+ with result code SQLITE_NOTFOUND if given an unknown database
+ name.
+ */
+ public boolean readOnly(String dbName){
+ final int rc = CApi.sqlite3_db_readonly(thisDb(), dbName);
+ if( 0==rc ) return false;
+ else if( rc>0 ) return true;
+ throw new SqliteException(CApi.SQLITE_NOTFOUND);
+ }
+
+ /**
+ Analog to sqlite3_db_release_memory().
+ */
+ public void releaseMemory(){
+ CApi.sqlite3_db_release_memory(thisDb());
+ }
+
+ /**
+ Analog to sqlite3_release_memory().
+ */
+ public static int releaseMemory(int n){
+ return CApi.sqlite3_release_memory(n);
+ }
+
+ /**
+ Analog to sqlite3_limit(). limitId must be one of the
+ LIMIT_... constants.
+
+ Returns the old limit for the given option. If newLimit is
+ negative, it returns the old limit without modifying the limit.
+
+ If sqlite3_limit() returns a negative value, this function throws
+ an SqliteException with the SQLITE_RANGE result code but no
+ further error info (because that case does not qualify as a
+ db-level error). Such errors may indicate an invalid argument
+ value or an invalid range for newLimit (the underlying function
+ does not differentiate between those).
+ */
+ public int limit(int limitId, int newLimit){
+ final int rc = CApi.sqlite3_limit(thisDb(), limitId, newLimit);
+ if( rc<0 ){
+ throw new SqliteException(CApi.SQLITE_RANGE);
}
+ return rc;
+ }
+
+ /**
+ Analog to sqlite3_errstr().
+ */
+ static String errstr(int resultCode){
+ return CApi.sqlite3_errstr(resultCode);
+ }
+
+ /**
+ A wrapper object for use with tableColumnMetadata(). They are
+ created and populated only via that interface.
+ */
+ public final class TableColumnMetadata {
+ Boolean pNotNull = null;
+ Boolean pPrimaryKey = null;
+ Boolean pAutoinc = null;
+ String pzCollSeq = null;
+ String pzDataType = null;
+
+ private TableColumnMetadata(){}
+
+ public String getDataType(){ return pzDataType; }
+ public String getCollation(){ return pzCollSeq; }
+ public boolean isNotNull(){ return pNotNull; }
+ public boolean isPrimaryKey(){ return pPrimaryKey; }
+ public boolean isAutoincrement(){ return pAutoinc; }
+ }
+
+ /**
+ Returns data about a database, table, and (optionally) column
+ (which may be null), as per sqlite3_table_column_metadata().
+ Throws if passed invalid arguments, else returns the result as a
+ new TableColumnMetadata object.
+ */
+ TableColumnMetadata tableColumnMetadata(
+ String zDbName, String zTableName, String zColumnName
+ ){
+ org.sqlite.jni.capi.OutputPointer.String pzDataType
+ = new org.sqlite.jni.capi.OutputPointer.String();
+ org.sqlite.jni.capi.OutputPointer.String pzCollSeq
+ = new org.sqlite.jni.capi.OutputPointer.String();
+ org.sqlite.jni.capi.OutputPointer.Bool pNotNull
+ = new org.sqlite.jni.capi.OutputPointer.Bool();
+ org.sqlite.jni.capi.OutputPointer.Bool pPrimaryKey
+ = new org.sqlite.jni.capi.OutputPointer.Bool();
+ org.sqlite.jni.capi.OutputPointer.Bool pAutoinc
+ = new org.sqlite.jni.capi.OutputPointer.Bool();
+ final int rc = CApi.sqlite3_table_column_metadata(
+ thisDb(), zDbName, zTableName, zColumnName,
+ pzDataType, pzCollSeq, pNotNull, pPrimaryKey, pAutoinc
+ );
+ checkRc(rc);
+ TableColumnMetadata rv = new TableColumnMetadata();
+ rv.pzDataType = pzDataType.value;
+ rv.pzCollSeq = pzCollSeq.value;
+ rv.pNotNull = pNotNull.value;
+ rv.pPrimaryKey = pPrimaryKey.value;
+ rv.pAutoinc = pAutoinc.value;
+ return rv;
}
/**
@@ -101,23 +425,44 @@ public final class Sqlite implements AutoCloseable {
public final class Stmt implements AutoCloseable {
private Sqlite _db = 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.
+ */
+ private final int resultColCount;
+
/** Only called by the prepare() factory functions. */
Stmt(Sqlite db, sqlite3_stmt stmt){
this._db = db;
this.stmt = stmt;
+ this.resultColCount = CApi.sqlite3_column_count(stmt);
}
sqlite3_stmt nativeHandle(){
return stmt;
}
- private sqlite3_stmt affirmOpen(){
+ /**
+ If this statement is still opened, its low-level handle is
+ returned, eelse an IllegalArgumentException is thrown.
+ */
+ private sqlite3_stmt thisStmt(){
if( null==stmt || 0==stmt.getNativePointer() ){
throw new IllegalArgumentException("This Stmt has been finalized.");
}
return stmt;
}
+ /** Throws if n is out of range of this.resultColCount. Intended
+ to be used by the columnXyz() methods. */
+ private sqlite3_stmt checkColIndex(int n){
+ if(n<0 || n>=this.resultColCount){
+ throw new IllegalArgumentException("Column index "+n+" is out of range.");
+ }
+ return thisStmt();
+ }
+
/**
Corresponds to sqlite3_finalize(), but we cannot override the
name finalize() here because this one requires a different
@@ -140,7 +485,9 @@ public final class Sqlite implements AutoCloseable {
/**
Throws if rc is any value other than 0, SQLITE_ROW, or
- SQLITE_DONE, else returns rc.
+ SQLITE_DONE, else returns rc. Error state for the exception is
+ extracted from this statement object (if it's opened) or the
+ string form of rc.
*/
private int checkRc(int rc){
switch(rc){
@@ -148,16 +495,33 @@ public final class Sqlite implements AutoCloseable {
case SQLITE_ROW:
case SQLITE_DONE: return rc;
default:
- throw new SqliteException(this);
+ if( null==stmt ) throw new SqliteException(rc);
+ else throw new SqliteException(this);
}
}
/**
- Works like sqlite3_step() but throws SqliteException for any
- result other than 0, SQLITE_ROW, or SQLITE_DONE.
+ Works like sqlite3_step() but returns true for SQLITE_ROW,
+ false for SQLITE_DONE, and throws SqliteException for any other
+ result.
*/
- public int step(){
- return checkRc(sqlite3_step(affirmOpen()));
+ public boolean step(){
+ switch(checkRc(sqlite3_step(thisStmt()))){
+ case CApi.SQLITE_ROW: return true;
+ case CApi.SQLITE_DONE: return false;
+ default:
+ throw new IllegalStateException(
+ "This \"cannot happen\": all possible result codes were checked already."
+ );
+ }
+ /*
+ Potential signature change TODO:
+
+ boolean step()
+
+ Returning true for SQLITE_ROW and false for anything else.
+ Those semantics have proven useful in the WASM/JS bindings.
+ */
}
public Sqlite db(){ return this._db; }
@@ -166,53 +530,109 @@ public final class Sqlite implements AutoCloseable {
Works like sqlite3_reset() but throws on error.
*/
public void reset(){
- checkRc(sqlite3_reset(affirmOpen()));
+ checkRc(CApi.sqlite3_reset(thisStmt()));
}
public void clearBindings(){
- sqlite3_clear_bindings( affirmOpen() );
+ CApi.sqlite3_clear_bindings( thisStmt() );
+ }
+ public void bindInt(int ndx, int val){
+ checkRc(CApi.sqlite3_bind_int(thisStmt(), ndx, val));
+ }
+ public void bindInt64(int ndx, long val){
+ checkRc(CApi.sqlite3_bind_int64(thisStmt(), ndx, val));
+ }
+ public void bindDouble(int ndx, double val){
+ checkRc(CApi.sqlite3_bind_double(thisStmt(), ndx, val));
+ }
+ public void bindObject(int ndx, Object o){
+ checkRc(CApi.sqlite3_bind_java_object(thisStmt(), ndx, o));
+ }
+ public void bindNull(int ndx){
+ checkRc(CApi.sqlite3_bind_null(thisStmt(), ndx));
+ }
+ public int bindParameterCount(){
+ return CApi.sqlite3_bind_parameter_count(thisStmt());
+ }
+ public int bindParameterIndex(String paramName){
+ return CApi.sqlite3_bind_parameter_index(thisStmt(), paramName);
+ }
+ public String bindParameterName(int ndx){
+ return CApi.sqlite3_bind_parameter_name(thisStmt(), ndx);
+ }
+ public void bindText(int ndx, byte[] utf8){
+ checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, utf8));
+ }
+ public void bindText(int ndx, String asUtf8){
+ checkRc(CApi.sqlite3_bind_text(thisStmt(), ndx, asUtf8));
+ }
+ public void bindText16(int ndx, byte[] utf16){
+ checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, utf16));
+ }
+ public void bindText16(int ndx, String txt){
+ checkRc(CApi.sqlite3_bind_text16(thisStmt(), ndx, txt));
+ }
+ public void bindZeroBlob(int ndx, int n){
+ checkRc(CApi.sqlite3_bind_zeroblob(thisStmt(), ndx, n));
+ }
+ public void bindBlob(int ndx, byte[] bytes){
+ checkRc(CApi.sqlite3_bind_blob(thisStmt(), ndx, bytes));
}
- }
-
-
- /**
- prepare() TODOs include:
-
- - overloads taking byte[] and ByteBuffer.
-
- - multi-statement processing, like CApi.sqlite3_prepare_multi()
- but using a callback specific to the higher-level Stmt class
- rather than the sqlite3_stmt class.
- */
- public Stmt prepare(String sql, int prepFlags){
- final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
- final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out);
- affirmRcOk(rc);
- return new Stmt(this, out.take());
- }
-
- public Stmt prepare(String sql){
- return prepare(sql, 0);
- }
-
- public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){
- int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
- new SqlFunction.ScalarAdapter(f));
- if( 0!=rc ) throw new SqliteException(db);
- }
-
- public void createFunction(String name, int nArg, ScalarFunction f){
- this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
- }
-
- public void createFunction(String name, int nArg, int eTextRep, AggregateFunction f ){
- int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
- new SqlFunction.AggregateAdapter(f));
- if( 0!=rc ) throw new SqliteException(db);
- }
- public void createFunction(String name, int nArg, AggregateFunction f){
- this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
- }
+ public byte[] columnBlob(int ndx){
+ return CApi.sqlite3_column_blob( checkColIndex(ndx), ndx );
+ }
+ public byte[] columnText(int ndx){
+ return CApi.sqlite3_column_text( checkColIndex(ndx), ndx );
+ }
+ public String columnText16(int ndx){
+ return CApi.sqlite3_column_text16( checkColIndex(ndx), ndx );
+ }
+ public int columnBytes(int ndx){
+ return CApi.sqlite3_column_bytes( checkColIndex(ndx), ndx );
+ }
+ public int columnBytes16(int ndx){
+ return CApi.sqlite3_column_bytes16( checkColIndex(ndx), ndx );
+ }
+ public int columnInt(int ndx){
+ return CApi.sqlite3_column_int( checkColIndex(ndx), ndx );
+ }
+ public long columnInt64(int ndx){
+ return CApi.sqlite3_column_int64( checkColIndex(ndx), ndx );
+ }
+ public double columnDouble(int ndx){
+ return CApi.sqlite3_column_double( checkColIndex(ndx), ndx );
+ }
+ public int columnType(int ndx){
+ return CApi.sqlite3_column_type( checkColIndex(ndx), ndx );
+ }
+ public String columnDeclType(int ndx){
+ return CApi.sqlite3_column_decltype( checkColIndex(ndx), ndx );
+ }
+ public int columnCount(){
+ return resultColCount;
+ }
+ public int columnDataCount(){
+ return CApi.sqlite3_data_count( thisStmt() );
+ }
+ public Object columnObject(int ndx){
+ return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx );
+ }
+ public <T> T columnObject(int ndx, Class<T> type){
+ return CApi.sqlite3_column_java_object( checkColIndex(ndx), ndx, type );
+ }
+ public String columnName(int ndx){
+ return CApi.sqlite3_column_name( checkColIndex(ndx), ndx );
+ }
+ public String columnDatabaseName(int ndx){
+ return CApi.sqlite3_column_database_name( checkColIndex(ndx), ndx );
+ }
+ public String columnOriginName(int ndx){
+ return CApi.sqlite3_column_origin_name( checkColIndex(ndx), ndx );
+ }
+ public String columnTableName(int ndx){
+ return CApi.sqlite3_column_table_name( checkColIndex(ndx), ndx );
+ }
+ } /* Stmt class */
}
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
index 111f004db..27cfc0e6b 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java
@@ -12,7 +12,7 @@
** This file is part of the wrapper1 interface for sqlite3.
*/
package org.sqlite.jni.wrapper1;
-import static org.sqlite.jni.capi.CApi.*;
+import org.sqlite.jni.capi.CApi;
import org.sqlite.jni.capi.sqlite3;
/**
@@ -22,10 +22,10 @@ import org.sqlite.jni.capi.sqlite3;
and C via JNI.
*/
public final class SqliteException extends java.lang.RuntimeException {
- int errCode = SQLITE_ERROR;
- int xerrCode = SQLITE_ERROR;
- int errOffset = -1;
- int sysErrno = 0;
+ private int errCode = CApi.SQLITE_ERROR;
+ private int xerrCode = CApi.SQLITE_ERROR;
+ private int errOffset = -1;
+ private int sysErrno = 0;
/**
Records the given error string and uses SQLITE_ERROR for both the
@@ -38,10 +38,13 @@ public final class SqliteException extends java.lang.RuntimeException {
/**
Uses sqlite3_errstr(sqlite3ResultCode) for the error string and
sets both the error code and extended error code to the given
- value.
+ value. This approach includes no database-level information and
+ systemErrno() will be 0, so is intended only for use with sqlite3
+ APIs for which a result code is not an error but which the
+ higher-level wrapper should treat as one.
*/
public SqliteException(int sqlite3ResultCode){
- super(sqlite3_errstr(sqlite3ResultCode));
+ super(CApi.sqlite3_errstr(sqlite3ResultCode));
errCode = xerrCode = sqlite3ResultCode;
}
@@ -50,16 +53,16 @@ public final class SqliteException extends java.lang.RuntimeException {
must refer to an opened db object). Note that this does NOT close
the db.
- Design note: closing the db on error is likely only useful during
+ Design note: closing the db on error is really only useful during
a failed db-open operation, and the place(s) where that can
happen are inside this library, not client-level code.
*/
SqliteException(sqlite3 db){
- super(sqlite3_errmsg(db));
- errCode = sqlite3_errcode(db);
- xerrCode = sqlite3_extended_errcode(db);
- errOffset = sqlite3_error_offset(db);
- sysErrno = sqlite3_system_errno(db);
+ super(CApi.sqlite3_errmsg(db));
+ errCode = CApi.sqlite3_errcode(db);
+ xerrCode = CApi.sqlite3_extended_errcode(db);
+ errOffset = CApi.sqlite3_error_offset(db);
+ sysErrno = CApi.sqlite3_system_errno(db);
}
/**
@@ -71,7 +74,7 @@ public final class SqliteException extends java.lang.RuntimeException {
}
public SqliteException(Sqlite.Stmt stmt){
- this( stmt.db() );
+ this(stmt.db());
}
public int errcode(){ return errCode; }
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
index f5fd5f84e..ca4e6d052 100644
--- a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
@@ -129,6 +129,11 @@ public class Tester2 implements Runnable {
execSql(db, String.join("", sql));
}
+ /**
+ Executes all SQL statements in the given string. If throwOnError
+ is true then it will throw for any prepare/step errors, else it
+ will return the corresponding non-0 result code.
+ */
public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
final sqlite3 db = dbw.nativeHandle();
OutputPointer.Int32 oTail = new OutputPointer.Int32();
@@ -145,7 +150,7 @@ public class Tester2 implements Runnable {
}
if( 0==sqlChunk.length ) break;
rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
- if(throwOnError) affirm(0 == rc);
+ if( throwOnError ) affirm(0 == rc);
else if( 0!=rc ) break;
pos = oTail.value;
stmt = outStmt.take();
@@ -158,7 +163,7 @@ public class Tester2 implements Runnable {
}
CApi.sqlite3_finalize(stmt);
affirm(0 == stmt.getNativePointer());
- if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){
+ if(CApi.SQLITE_DONE!=rc){
break;
}
}
@@ -194,9 +199,9 @@ public class Tester2 implements Runnable {
}
Sqlite openDb(String name){
- final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE|
- CApi.SQLITE_OPEN_CREATE|
- CApi.SQLITE_OPEN_EXRESCODE);
+ final Sqlite db = Sqlite.open(name, Sqlite.OPEN_READWRITE|
+ Sqlite.OPEN_CREATE|
+ Sqlite.OPEN_EXRESCODE);
++metrics.dbOpen;
return db;
}
@@ -224,19 +229,50 @@ public class Tester2 implements Runnable {
void testPrepare1(){
try (Sqlite db = openDb()) {
- Sqlite.Stmt stmt = db.prepare("SELECT 1");
+ Sqlite.Stmt stmt = db.prepare("SELECT ?1");
+ Exception e = null;
affirm( null!=stmt.nativeHandle() );
- affirm( CApi.SQLITE_ROW == stmt.step() );
- affirm( CApi.SQLITE_DONE == stmt.step() );
+ affirm( 1==stmt.bindParameterCount() );
+ affirm( "?1".equals(stmt.bindParameterName(1)) );
+ affirm( null==stmt.bindParameterName(2) );
+ stmt.bindInt64(1, 1);
+ stmt.bindDouble(1, 1.1);
+ stmt.bindObject(1, db);
+ stmt.bindNull(1);
+ stmt.bindText(1, new byte[] {32,32,32});
+ stmt.bindText(1, "123");
+ stmt.bindText16(1, "123".getBytes(StandardCharsets.UTF_16));
+ stmt.bindText16(1, "123");
+ stmt.bindZeroBlob(1, 8);
+ stmt.bindBlob(1, new byte[] {1,2,3,4});
+ stmt.bindInt(1, 17);
+ try{ stmt.bindInt(2,1); }
+ catch(Exception ex){ e = ex; }
+ affirm( null!=e );
+ e = null;
+ affirm( stmt.step() );
+ try{ stmt.columnInt(1); }
+ catch(Exception ex){ e = ex; }
+ affirm( null!=e );
+ e = null;
+ affirm( 17 == stmt.columnInt(0) );
+ affirm( 17L == stmt.columnInt64(0) );
+ affirm( 17.0 == stmt.columnDouble(0) );
+ affirm( "17".equals(stmt.columnText16(0)) );
+ affirm( !stmt.step() );
stmt.reset();
- affirm( CApi.SQLITE_ROW == stmt.step() );
- affirm( CApi.SQLITE_DONE == stmt.step() );
+ affirm( stmt.step() );
+ affirm( !stmt.step() );
affirm( 0 == stmt.finalizeStmt() );
affirm( null==stmt.nativeHandle() );
- stmt = db.prepare("SELECT 1");
- affirm( CApi.SQLITE_ROW == stmt.step() );
- affirm( 0 == stmt.finalizeStmt() )
+ stmt = db.prepare("SELECT ?");
+ stmt.bindObject(1, db);
+ affirm( stmt.step() );
+ affirm( db==stmt.columnObject(0) );
+ affirm( db==stmt.columnObject(0, Sqlite.class ) );
+ affirm( null==stmt.columnObject(0, Sqlite.Stmt.class ) );
+ affirm( 0==stmt.finalizeStmt() )
/* getting a non-0 out of sqlite3_finalize() is tricky */;
affirm( null==stmt.nativeHandle() );
}
@@ -270,7 +306,7 @@ public class Tester2 implements Runnable {
void testUdfAggregate(){
final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
- final ValueHolder<Integer> vh = new ValueHolder<>(0);
+ Sqlite.Stmt q = null;
try (Sqlite db = openDb()) {
execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
final AggregateFunction f = new AggregateFunction<Integer>(){
@@ -284,18 +320,96 @@ public class Tester2 implements Runnable {
final Integer v = this.takeAggregateState(args);
if( null==v ) args.resultNull();
else args.resultInt(v);
- vh.value = v;
}
public void xDestroy(){
++xDestroyCalled.value;
}
};
- db.createFunction("myagg", -1, f);
- execSql(db, "select myagg(a) from t");
- affirm( 6 == vh.value );
+ db.createFunction("summer", 1, f);
+ q = db.prepare(
+ "with cte(v) as ("+
+ "select 3 union all select 5 union all select 7"+
+ ") select summer(v), summer(v+1) from cte"
+ /* ------------------^^^^^^^^^^^ ensures that we're handling
+ sqlite3_aggregate_context() properly. */
+ );
+ affirm( q.step() );
+ affirm( 15==q.columnInt(0) );
+ q.finalizeStmt();
+ q = null;
affirm( 0 == xDestroyCalled.value );
+ db.createFunction("summerN", -1, f);
+
+ q = db.prepare("select summerN(1,8,9), summerN(2,3,4)");
+ affirm( q.step() );
+ affirm( 18==q.columnInt(0) );
+ affirm( 9==q.columnInt(1) );
+ q.finalizeStmt();
+ q = null;
+
+ }/*db*/
+ finally{
+ if( null!=q ) q.finalizeStmt();
}
- affirm( 1 == xDestroyCalled.value );
+ affirm( 2 == xDestroyCalled.value
+ /* because we've bound the same instance twice */ );
+ }
+
+ private void testUdfWindow(){
+ final Sqlite db = openDb();
+ /* Example window function, table, and results taken from:
+ https://sqlite.org/windowfunctions.html#udfwinfunc */
+ final WindowFunction func = new WindowFunction<Integer>(){
+ //! Impl of xStep() and xInverse()
+ private void xStepInverse(SqlFunction.Arguments args, int v){
+ this.getAggregateState(args,0).value += v;
+ }
+ @Override public void xStep(SqlFunction.Arguments args){
+ this.xStepInverse(args, args.getInt(0));
+ }
+ @Override public void xInverse(SqlFunction.Arguments args){
+ this.xStepInverse(args, -args.getInt(0));
+ }
+ //! Impl of xFinal() and xValue()
+ private void xFinalValue(SqlFunction.Arguments args, Integer v){
+ if(null == v) args.resultNull();
+ else args.resultInt(v);
+ }
+ @Override public void xFinal(SqlFunction.Arguments args){
+ xFinalValue(args, this.takeAggregateState(args));
+ affirm( null == this.getAggregateState(args,null).value );
+ }
+ @Override public void xValue(SqlFunction.Arguments args){
+ xFinalValue(args, this.getAggregateState(args,null).value);
+ }
+ };
+ db.createFunction("winsumint", 1, func);
+ execSql(db, new String[] {
+ "CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
+ "('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
+ });
+ final Sqlite.Stmt stmt = db.prepare(
+ "SELECT x, winsumint(y) OVER ("+
+ "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
+ ") AS sum_y "+
+ "FROM twin ORDER BY x;"
+ );
+ int n = 0;
+ while( stmt.step() ){
+ final String s = stmt.columnText16(0);
+ final int i = stmt.columnInt(1);
+ switch(++n){
+ case 1: affirm( "a".equals(s) && 9==i ); break;
+ case 2: affirm( "b".equals(s) && 12==i ); break;
+ case 3: affirm( "c".equals(s) && 16==i ); break;
+ case 4: affirm( "d".equals(s) && 12==i ); break;
+ case 5: affirm( "e".equals(s) && 9==i ); break;
+ default: affirm( false /* cannot happen */ );
+ }
+ }
+ stmt.close();
+ affirm( 5 == n );
+ db.close();
}
private void runTests(boolean fromThread) throws Exception {
diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
new file mode 100644
index 000000000..479fc74d7
--- /dev/null
+++ b/ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java
@@ -0,0 +1,46 @@
+/*
+** 2023-10-16
+**
+** 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 is part of the wrapper1 interface for sqlite3.
+*/
+package org.sqlite.jni.wrapper1;
+import org.sqlite.jni.capi.CApi;
+import org.sqlite.jni.annotation.*;
+import org.sqlite.jni.capi.sqlite3_context;
+import org.sqlite.jni.capi.sqlite3_value;
+
+/**
+ A SqlFunction implementation for window functions. The T type
+ represents the type of data accumulated by this function while it
+ works. e.g. a SUM()-like UDF might use Integer or Long and a
+ CONCAT()-like UDF might use a StringBuilder or a List<String>.
+*/
+public abstract class WindowFunction<T> extends AggregateFunction<T> {
+
+ /**
+ As for the xInverse() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, the
+ exception is reported via sqlite3_result_error().
+ */
+ public abstract void xInverse(SqlFunction.Arguments args);
+
+ /**
+ As for the xValue() argument of the C API's
+ sqlite3_create_window_function(). If this function throws, it is
+ translated into sqlite3_result_error().
+
+ Note that the passed-in object will not actually contain any
+ arguments for xValue() but will contain the context object needed
+ for setting the call's result or error state.
+ */
+ public abstract void xValue(SqlFunction.Arguments args);
+
+}
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
index ad2872d83..57cd61eb9 100644
--- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
+++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
@@ -63,6 +63,7 @@ _sqlite3_file_control
_sqlite3_finalize
_sqlite3_free
_sqlite3_get_auxdata
+_sqlite3_get_autocommit
_sqlite3_initialize
_sqlite3_keyword_count
_sqlite3_keyword_name
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
index f23a02366..29efb3e07 100644
--- a/ext/wasm/api/sqlite3-api-glue.js
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -188,6 +188,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
["sqlite3_finalize", "int", "sqlite3_stmt*"],
["sqlite3_free", undefined,"*"],
+ ["sqlite3_get_autocommit", "int", "sqlite3*"],
["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"],
["sqlite3_initialize", undefined],
/*["sqlite3_interrupt", undefined, "sqlite3*"
diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js
index 29f7d2be6..7c65dd1d3 100644
--- a/ext/wasm/api/sqlite3-api-worker1.js
+++ b/ext/wasm/api/sqlite3-api-worker1.js
@@ -62,7 +62,7 @@
```
{
- type: string, // one of: 'open', 'close', 'exec', 'config-get'
+ type: string, // one of: 'open', 'close', 'exec', 'export', 'config-get'
messageId: OPTIONAL arbitrary value. The worker will copy it as-is
into response messages to assist in client-side dispatching.
@@ -325,6 +325,37 @@
passed only a string), noting that options.resultRows and
options.columnNames may be populated by the call to db.exec().
+
+ ====================================================================
+ "export" the current db
+
+ To export the underlying database as a byte array...
+
+ Message format:
+
+ ```
+ {
+ type: "export",
+ messageId: ...as above...,
+ dbId: ...as above...
+ }
+ ```
+
+ Response:
+
+ ```
+ {
+ type: "export",
+ messageId: ...as above...,
+ dbId: ...as above...
+ result: {
+ byteArray: Uint8Array (as per sqlite3_js_db_export()),
+ filename: the db filename,
+ mimetype: "application/x-sqlite3"
+ }
+ }
+ ```
+
*/
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
sqlite3.initWorker1API = function(){
diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js
index 92d763f1b..c26bce25b 100644
--- a/ext/wasm/tester1.c-pp.js
+++ b/ext/wasm/tester1.c-pp.js
@@ -2644,13 +2644,17 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
T.assert( 0 === rc /*void pointer*/ );
// Commit hook...
+ T.assert( 0!=capi.sqlite3_get_autocommit(db) );
db.exec("BEGIN; SELECT 1; COMMIT");
T.assert(0 === countCommit,
"No-op transactions (mostly) do not trigger commit hook.");
db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT");
T.assert(1 === countCommit,
"But EXCLUSIVE transactions do.");
- db.transaction((d)=>{d.exec("create table t(a)");});
+ db.transaction((d)=>{
+ T.assert( 0==capi.sqlite3_get_autocommit(db) );
+ d.exec("create table t(a)");
+ });
T.assert(2 === countCommit);
// Rollback hook:
diff --git a/manifest b/manifest
index 9e8d77f1e..362a98e52 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sa\sproblem\swith\samalgamation\sbuilds\son\sthis\sbranch.
-D 2023-11-02T18:10:22.533
+C Add\sthe\s"remove_diacritics"\soption\sto\sthe\sfts5\strigram\stokenizer.
+D 2023-11-02T18:22:35.452
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -237,20 +237,20 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
-F ext/jni/GNUmakefile 5c3ac326bf3853486ebe0d70819abc790cc65c412182ce4ebd5012b008d9b059
+F ext/jni/GNUmakefile 36919b7c4fb8447da4330df9996c7b064b766957f8b7be214a30eab55a8b8072
F ext/jni/README.md ef9ac115e97704ea995d743b4a8334e23c659e5534c3b64065a5405256d5f2f4
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
-F ext/jni/src/c/sqlite3-jni.c 6f6df9657989e9ca2cfdcc2fe9a71c279de56d5c941adfd09a0f24256de35c8f
-F ext/jni/src/c/sqlite3-jni.h b4c413a0d0c734683da1049cfcf89e35ae2719759d0656ec0f8c57188f18cab8
+F ext/jni/src/c/sqlite3-jni.c afe9c25b82279a28fe2c81f869070fa0d434b0a8ccd7f8aca0e8173db410d14a
+F ext/jni/src/c/sqlite3-jni.h 1c45fd4689cec42f3d84d2fee41bb494016a12fcb5fd80291095590666a14015
F ext/jni/src/org/sqlite/jni/annotation/NotNull.java a99341e88154e70447596b1af6a27c586317df41a7e0f246fd41370cd7b723b2
F ext/jni/src/org/sqlite/jni/annotation/Nullable.java 0b1879852707f752512d4db9d7edd0d8db2f0c2612316ce1c832715e012ff6ba
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/AggregateFunction.java bc29e986c866c2ddbbb9f935f5b7264c1c1026864e50a4a735192864f75e37c0
+F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63418129656daa9a9f30e7e7be982bd5ab394b1dbd0
F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013
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/CApi.java bccb442ca81cd4decb1adae99006a60b7a9f54e5153842e738c01104e97d1de0
+F ext/jni/src/org/sqlite/jni/capi/CApi.java 24aba7b14b11da52cd47083608d37c186122c2e2072e75b2ff923d1f15bfb9e5
F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 0bfd6e56e8265c2f05c9207665707285534d78f8466ef0e0430c65677f00943d
F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95
@@ -264,11 +264,11 @@ F ext/jni/src/org/sqlite/jni/capi/PreupdateHookCallback.java 819d938e26208adde17
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/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0
-F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java fef556adbc3624292423083a648bdf97fa8a4f6b3b6577c9660dd7bd6a6d3c4a
+F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385155fa3b8011a5cca0bb3c28468c7131c1a5
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/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f
-F ext/jni/src/org/sqlite/jni/capi/Tester1.java ca195521b6bda3e0cd00e76bb71ec8060d1fab76a2f13b1af9feea40789f44bb
+F ext/jni/src/org/sqlite/jni/capi/Tester1.java b6b2f3354ba68956a6bcd1c586b8eb25a0bd66eed2b58b340405e1129da15de9
F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4
F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 9f9e151f1da017b706c0ee5f40f4c86b54e773d6ae4339723e0cc85a456251ab
@@ -292,13 +292,14 @@ F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653
F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978
F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90
F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
-F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java 5ad99bd74c85f56bbef324d9ec29b4048f4620547c9a80093d8586c3557f9f9a
+F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java bbe60ac7fd8718edb215a23dc901771bcedb1df3b46d9cf6caff6f419828587f
F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
-F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 004394eeb944baa56e36cd7ae69ba6d4a52b52db3c49439db16e98270b861421
-F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java a9ddc6a9e8c113168cc67592ae24c0e56d30dd06226eeab012f2761a0889d7bb
-F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1386f7b753134fc12253ce2fbbc448ba8c970567fac01a3356cb672e14408d73
-F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java c24b510ebe801c30533cc62efdf69a4a5e2da9ec4b49f8d403f2060693f060a0
+F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 9be20f3ba6edbe63285224d5d640dbfb0d84fb8cf5134084e3ae40004ec9d6d9
+F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java fae71b7454fa3d9243ad26c80e55b590c044a0f0a23d18cae21f0cfa3a92a969
+F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java aa85b4b05fae240b14f3d332f9524a2f80c619fb03856be72b4adda866b63b72
+F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 13008f8d3c34c1dd55c3afe6dd18dcf94316874cde893ab0661a973fc51a87a4
F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
+F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java 1a1afbafbd7406ff67e7d6405541c6347517c731de535a97d7a3df1d4db835b4
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/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0
@@ -570,7 +571,7 @@ F ext/wasm/SQLTester/SQLTester.mjs ec2f6ba63a0f2f0562941a0fb8e46b7dc55589711513f
F ext/wasm/SQLTester/SQLTester.run.mjs c72b7fe2072d05992f7a3d8c6a1d34e95712513ceabe40849784e24e41c84638
F ext/wasm/SQLTester/index.html 3f8a016df0776be76605abf20e815ecaafbe055abac0e1fe5ea080e7846b760d
F ext/wasm/SQLTester/touint8array.c 2d5ece04ec1393a6a60c4bf96385bda5e1a10ad49f3038b96460fc5e5aa7e536
-F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab
+F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api f442460ed9a109e637dd3ea1caa4489553ad9414e8988118b208bb7a4bbece6b
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-see fb29e62082a658f0d81102488414d422c393c4b20cc2f685b216bc566237957b
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
F ext/wasm/api/README.md 5eb44fa02e9c693a1884a3692428647894b0380b24bca120866b7a24c8786134
@@ -580,10 +581,10 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08
F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62
F ext/wasm/api/pre-js.c-pp.js ad906703f7429590f2fbf5e6498513bf727a1a4f0ebfa057afb08161d7511219
F ext/wasm/api/sqlite3-api-cleanup.js d235ad237df6954145404305040991c72ef8b1881715d2a650dda7b3c2576d0e
-F ext/wasm/api/sqlite3-api-glue.js 26aedfb27915f4f316f6eac84078443e4f0d2dfe5f012310014923ed4b77b2b6
+F ext/wasm/api/sqlite3-api-glue.js 119b91c8a7ce6648679eb66fcdd1ed07ef7fd892eb501d658fbfefcc962012d9
F ext/wasm/api/sqlite3-api-oo1.js 9678dc4d9a5d39632b6ffe6ea94a023119260815bf32f265bf5f6c36c9516db8
F ext/wasm/api/sqlite3-api-prologue.js 9aeba7b45cf41b3a26d34d7fb2525633cd1adfc544888c1ea8dbb077496f4ce9
-F ext/wasm/api/sqlite3-api-worker1.js f941382f21006b4a817754184e2661b0a63ce650201f3419cd60f4758b6fd60e
+F ext/wasm/api/sqlite3-api-worker1.js 88770ac01fc756f89a3e060eec17111d6c1688e89ebfd34cb9d9e54d25affbb9
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
F ext/wasm/api/sqlite3-opfs-async-proxy.js 8cf8a897726f14071fae6be6648125162b256dfb4f96555b865dbb7a6b65e379
F ext/wasm/api/sqlite3-v-helper.js 7daa0eab0a513a25b05e9abae7b5beaaa39209b3ed12f86aeae9ef8d2719ed25
@@ -634,7 +635,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b
F ext/wasm/tester1-worker.html ebc4b820a128963afce328ecf63ab200bd923309eb939f4110510ab449e9814c
F ext/wasm/tester1.c-pp.html 1c1bc78b858af2019e663b1a31e76657b73dc24bede28ca92fbe917c3a972af2
-F ext/wasm/tester1.c-pp.js fb20d9e1c308ea34a29d8afdda1a6c5edc406241b5560fa23c19c14747ee41bc
+F ext/wasm/tester1.c-pp.js d628826e936bd143d64e0fa3089752abeeeea38a34a7e2b18d364f090d4e99c6
F ext/wasm/tests/opfs/concurrency/index.html 0802373d57034d51835ff6041cda438c7a982deea6079efd98098d3e42fbcbc1
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@@ -795,7 +796,7 @@ F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
F src/vdbe.c 14479441337135eed8e290fb1d4abb7db657d93217a3b1ea8a2f031d3895536a
F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0
F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c
-F src/vdbeapi.c fe654b1f54e1feebcaed6c2ae3ed035cc65bfeb9a1169bed866abc42bfc63ff6
+F src/vdbeapi.c db190d007bdf5b9165edeb12369f4c59a459f88fd652c1671c1238862e662cc3
F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993
F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5
F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b
@@ -2140,8 +2141,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 83da80135b6105f47d1de560232449562ae8ac176c8011a6f75589f62bc9b1db
-R 3ce25e9cac6413ed342178c779ca3982
+P e8c97faec339430fa2d1a7e915050d9b363bdba606a4419397cb099141da254d 8f046c82c9cf51fc349674577c68d3d2499ee37009deacbf937d711d9930fd49
+R e431cbffcb397fe64b4dba15715dbe90
+T +closed 8f046c82c9cf51fc349674577c68d3d2499ee37009deacbf937d711d9930fd49
U dan
-Z b71b576bf0dbf7927d3f2659bd9c8691
+Z a985144f5adb4bfbc320c60d16272d9c
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index db15fd6e8..f8430c08f 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-8f046c82c9cf51fc349674577c68d3d2499ee37009deacbf937d711d9930fd49 \ No newline at end of file
+0d50172477064dce3f7c61641d88aa15d8a1edea9872adaffcc1a7a1c9ab0e93 \ No newline at end of file
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index 31ebbc697..6724035fd 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -152,7 +152,15 @@ int sqlite3_clear_bindings(sqlite3_stmt *pStmt){
int rc = SQLITE_OK;
Vdbe *p = (Vdbe*)pStmt;
#if SQLITE_THREADSAFE
- sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex;
+ sqlite3_mutex *mutex;
+#endif
+#ifdef SQLITE_ENABLE_API_ARMOR
+ if( pStmt==0 ){
+ return SQLITE_MISUSE_BKPT;
+ }
+#endif
+#if SQLITE_THREADSAFE
+ mutex = p->db->mutex;
#endif
sqlite3_mutex_enter(mutex);
for(i=0; i<p->nVar; i++){