.
+
+ 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.
+
+
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.
+
+
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 {
+ private final java.util.Map> 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
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){
+ final Long key = cx.getAggregateContext(true);
+ ValueHolder 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 h = map.remove(cx.getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
/** Per-invocation state for the UDF. */
- private final SQLFunction.PerContextState map =
- new SQLFunction.PerContextState<>();
+ private final PerContextState 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 302cdb760e..6eeeb29a24 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;
The C-side part can be found in sqlite3-jni.c.
-
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.
-
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 sqlite3_column_java_object(
+ @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class 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 sqlite3_value_java_casted(@NotNull sqlite3_value v,
+ public static T sqlite3_value_java_object(@NotNull sqlite3_value v,
@NotNull Class 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 4806e2fc0c..7ad1381a7a 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.
-
- T must be of a type which can be legally stored as a value in
- java.util.HashMap.
-
- 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.
-
-
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.
-
-
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 {
- private final java.util.Map> 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
- which can be used to modify that state directly without
- requiring that the client update the underlying map's entry.
-
- The caller is obligated to eventually call
- takeAggregateState() to clear the mapping.
- */
- public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){
- final Long key = cx.getAggregateContext(true);
- ValueHolder 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 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 6fb28e65b9..4657185658 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 173d775e62..6a38d4b530 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 implements SqlFunction {
*/
public void xDestroy() {}
+ /**
+ PerContextState assists aggregate and window functions in
+ managing their accumulator state across calls to the UDF's
+ callbacks.
+
+ T must be of a type which can be legally stored as a value in
+ java.util.HashMap.
+
+ 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.
+
+
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.
+
+
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 {
+ private final java.util.Map> 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
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
+ */
+ public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){
+ final Long key = args.getContext().getAggregateContext(true);
+ ValueHolder 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 h = map.remove(args.getContext().getAggregateContext(false));
+ return null==h ? null : h.value;
+ }
+ }
+
/** Per-invocation state for the UDF. */
- private final SqlFunction.PerContextState map =
- new SqlFunction.PerContextState<>();
+ private final PerContextState 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 d6acda5aa5..2d0ebfaf32 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
@@ -49,6 +60,83 @@ public interface SqlFunction {
this.length = this.args.length;
}
+ /**
+ Returns the sqlite3_value at the given argument index or throws
+ an IllegalArgumentException exception if ndx is out of range.
+ */
+ private sqlite3_value valueAt(int ndx){
+ if(ndx<0 || ndx>=args.length){
+ throw new IllegalArgumentException(
+ "SQL function argument index "+ndx+" is out of range."
+ );
+ }
+ 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 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 getObject(int argNdx, Class type){
+ return CApi.sqlite3_value_java_object(valueAt(argNdx), type);
+ }
+
+ 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); }
+ public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
+ public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
+ public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
+ public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
+ public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
+ 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.
+ CApi.sqlite3_result_zeroblob64(cx, n);
+ }
+
+ public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
+ public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
+ public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
+ 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 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(argNdx);
+ CApi.sqlite3_set_auxdata(cx, argNdx, o);
+ }
+
+ public Object getAuxData(int argNdx){
+ valueAt(argNdx);
+ return CApi.sqlite3_get_auxdata(cx, argNdx);
+ }
+
/**
Wrapper for a single SqlFunction argument. Primarily intended
for use with the Arguments class's Iterable interface.
@@ -72,7 +160,7 @@ public interface SqlFunction {
public int getBytes(){return a.getBytes(ndx);}
public int getBytes16(){return a.getBytes16(ndx);}
public Object getObject(){return a.getObject(ndx);}
- public T getObjectCasted(Class type){ return a.getObjectCasted(ndx, type); }
+ public T getObject(Class 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);}
@@ -87,147 +175,6 @@ public interface SqlFunction {
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.
- */
- private sqlite3_value valueAt(int ndx){
- if(ndx<0 || ndx>=args.length){
- throw new IllegalArgumentException(
- "SQL function argument index "+ndx+" is out of range."
- );
- }
- return args[ndx];
- }
-
- 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 getObjectCasted(int arg, Class type){
- return CApi.sqlite3_value_java_casted(valueAt(arg), 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 void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
- public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
- public void resultDouble(double v){ CApi.sqlite3_result_double(cx, v); }
- public void resultError(String msg){CApi.sqlite3_result_error(cx, msg);}
- public void resultError(Exception e){CApi.sqlite3_result_error(cx, e);}
- public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
- public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
- 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 resultZeroBlob(long n){
- // Throw on error? If n is too big,
- // sqlite3_result_error_toobig() is automatically called.
- CApi.sqlite3_result_zeroblob64(cx, n);
- }
-
- public void resultBlob(byte[] blob){CApi.sqlite3_result_blob(cx, blob);}
- public void resultText(byte[] utf8){CApi.sqlite3_result_text(cx, utf8);}
- public void resultText(String txt){CApi.sqlite3_result_text(cx, txt);}
- 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){
- /* 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);
- }
-
- public Object getAuxData(int arg){
- valueAt(arg);
- return CApi.sqlite3_get_auxdata(cx, arg);
- }
- }
-
- /**
- PerContextState assists aggregate and window functions in
- managing their accumulator state across calls to the UDF's
- callbacks.
-
- T must be of a type which can be legally stored as a value in
- java.util.HashMap.
-
- 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.
-
-
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.
-
-
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 {
- private final java.util.Map> 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
- which can be used to modify that state directly without
- requiring that the client update the underlying map's entry.
-
- The caller is obligated to eventually call
- takeAggregateState() to clear the mapping.
- */
- public ValueHolder getAggregateState(SqlFunction.Arguments args, T initialValue){
- final Long key = args.getContext().getAggregateContext(true);
- ValueHolder 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 h = map.remove(args.getContext().getAggregateContext(false));
- return null==h ? null : h.value;
- }
}
/**
@@ -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 bcf97b2394..b464bd7d5c 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();
@@ -70,6 +129,22 @@ 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
@@ -77,7 +152,7 @@ public final class Sqlite implements AutoCloseable {
*/
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,12 +163,261 @@ 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;
+ }
+
/**
Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
create new instances.
@@ -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 columnObject(int ndx, Class 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 111f004db4..27cfc0e6bb 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 f5fd5f84e6..ca4e6d0521 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 xDestroyCalled = new ValueHolder<>(0);
- final ValueHolder 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(){
@@ -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(){
+ //! 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 0000000000..479fc74d7f
--- /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.
+*/
+public abstract class WindowFunction extends AggregateFunction {
+
+ /**
+ 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 ad2872d839..57cd61eb9d 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 f23a02366b..29efb3e07b 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 29f7d2be63..7c65dd1d3e 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 92d763f1ba..c26bce25be 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 a1f4d26271..869a09d63f 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Bring\sthe\sjsonb\sbranch\sup-to-date\swith\sversion\s3.44.0\schanges.
-D 2023-11-01T14:12:07.551
+C Merge\sall\sthe\slatest\strunk\sfixes\sand\senhancements\sinto\sthe\sjsonb\sbranch.
+D 2023-11-03T11:35:33.434
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -7,7 +7,7 @@ F Makefile.in 8b59912fc1538f96a08555605c5886cdcc733696ae7f22e374b2a4752196ca20
F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6
F Makefile.msc f0cf219350d9af4fba411b4f6306dce2adc897484e8f446de1fb4f40de674d00
F README.md 963d30019abf0cc06b263cd2824bce022893f3f93a531758f6f04ff2194a16a8
-F VERSION 4c09b629c03b8ae32317cb336a32f3aa3252841d6dcd51184cecc4278d08f21e
+F VERSION 73573d4545343f001bf5dc5461173a7c78c203dd046cabcf99153878cf25d3a6
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2
F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90
@@ -22,7 +22,7 @@ F autoconf/configure.ac ec7fa914c5e74ff212fe879f9bb6918e1234497e05facfb641f30c4d
F autoconf/tea/Makefile.in 106a96f2f745d41a0f6193f1de98d7355830b65d45032c18cd7c90295ec24196
F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873
F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43
-F autoconf/tea/configure.ac 1ee185f2f8c2e9bed69d0113222724202f962d04eafd57197e65c662d2d9b180
+F autoconf/tea/configure.ac 4c32b08691a5b296206b38422b53b92b65be3d3f6b3dd6552a50981a61f5acda
F autoconf/tea/doc/sqlite3.n e1fe45d4f5286ee3d0ccc877aca2a0def488e9bb
F autoconf/tea/license.terms 13bd403c9610fd2b76ece0ab50c4c5eda933d523
F autoconf/tea/pkgIndex.tcl.in b9eb6dd37f64e08e637d576b3c83259814b9cddd78bec4af2e5abfc6c5c750ce
@@ -33,11 +33,11 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034
F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63
F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6
F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559
-F configure 1d9cbcb416cb5387b3f750ae6db63cfedb703a2e46bf8ca61daecff31d208252 x
-F configure.ac de31fea7d975bb7ebafbe0e2190a855cc80d48558bf0c9a6578a1836daf1cd3a
+F configure bcb1042e92775424a1021d2f4c89c78a699a6225df01fa8c593df7df0be6ad10 x
+F configure.ac f25bd7843120f2c2b8bc9db5a92b0502bbdd28e68907415c3d42fc8e57c657b9
F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad
F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd
-F doc/compile-for-windows.md 922ba580d210a1f1bd3ef9d0413121556f9b5714fb5c01e664d980f85fa4ac8c
+F doc/compile-for-windows.md 50b27d77be96195c66031a3181cb8684ed822327ea834e07f9c014213e5e3bcf
F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f
F doc/lemon.html 44a53a1d2b42d7751f7b2f478efb23c978e258d794bfd172442307a755b9fa44
F doc/pager-invariants.txt 27fed9a70ddad2088750c4a2b493b63853da2710
@@ -100,7 +100,7 @@ F ext/fts5/fts5_storage.c 5d10b9bdcce5b90656cad13c7d12ad4148677d4b9e3fca0481fca5
F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae
F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
-F ext/fts5/fts5_tokenize.c 5e251efb0f1af99a25ed50010ba6b1ad1250aca5921af1988fdcabe5ebc3cb43
+F ext/fts5/fts5_tokenize.c 83cfcede3898001cab84432a36ce1503e3080cf9b1c682b022ec82e267ea4c13
F ext/fts5/fts5_unicode2.c eca63dbc797f8ff0572e97caf4631389c0ab900d6364861b915bdd4735973f00
F ext/fts5/fts5_varint.c e64d2113f6e1bfee0032972cffc1207b77af63319746951bf1d09885d1dadf80
F ext/fts5/fts5_vocab.c aed56169ae5c1aa9b8189c779ffeef04ed516d3c712c06914e6d91a6759f4e4a
@@ -217,6 +217,7 @@ F ext/fts5/test/fts5tok1.test 1f7817499f5971450d8c4a652114b3d833393c8134e32422d0
F ext/fts5/test/fts5tok2.test dcacb32d4a2a3f0dd3215d4a3987f78ae4be21a2
F ext/fts5/test/fts5tokenizer.test ac3c9112b263a639fb0508ae73a3ee886bf4866d2153771a8e8a20c721305a43
F ext/fts5/test/fts5trigram.test 6c4e37864f3e7d90673db5563d9736d7e40080ab94d10ebdffa94c1b77941da0
+F ext/fts5/test/fts5trigram2.test 9fe4207f8a4241747aff1005258b564958588d21bfd240d6cd4c2e955d31c156
F ext/fts5/test/fts5ubsan.test 783d5a8d13ebfa169e634940228db54540780e3ba7a87ad1e4510e61440bf64b
F ext/fts5/test/fts5umlaut.test a42fe2fe6387c40c49ab27ccbd070e1ae38e07f38d05926482cc0bccac9ad602
F ext/fts5/test/fts5unicode.test 17056f4efe6b0a5d4f41fdf7a7dc9af2873004562eaa899d40633b93dc95f5a9
@@ -236,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
@@ -263,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
@@ -291,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
@@ -569,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
@@ -579,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
@@ -633,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
@@ -719,14 +721,14 @@ F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
F src/prepare.c bde74add20fc0e8ce0c4e937a1f70a36d17413afe4f71d3e103f5cb74b17c8d9
F src/printf.c 9da63b9ae1c14789bcae12840f5d800fd9302500cd2d62733fac77f0041b4750
F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
-F src/resolve.c 31229276a8eb5b5de1428cd2d80f6f1cf8ffc5248be25e47cf575df12f1b8f23
+F src/resolve.c d017bad7ba8e778617701a0e986fdeb393d67d6afa84fb28ef4e8b8ad2acf916
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
-F src/select.c a19daa26e95f7245106a31f288b2f50c72d1f2cc156703f04c8c91450e111515
-F src/shell.c.in 3e9371ca6a93294931a8ed8b098bc3cb15d57567aa9a1f2ade72db7f5c572795
+F src/select.c 8ebbc67478e2d43f0b24c79258597646e526c5001d07801bba7d4059088c8b12
+F src/shell.c.in aebfbedaa7706d2c73ab7366a19fc8bc40ea68b70d2529f7feef54e6eb077ea2
F src/sqlite.h.in ef0e41e83ad1ac0dcc9ec9939bf541a44b1c5de821bee2d6c61754c3252f3276
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 3f046c04ea3595d6bfda99b781926b17e672fd6d27da2ba6d8d8fc39981dcb54
-F src/sqliteInt.h 567e317f8631883897b7d3da43fce778b7c30dd0dd7f714558c9725fc1c1196c
+F src/sqliteInt.h 707095a0591e02f4866ed9798cbdd97a8ae8cf4d98f061808944c2cd1c95d1a9
F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6
F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -794,7 +796,7 @@ F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104
F src/vdbe.c 4c76111311e06e74d2519f9cfee5caaaee722bf04559e16f4758a18a0c723b6c
F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0
F src/vdbeInt.h 949669dfd8a41550d27dcb905b494f2ccde9a2e6c1b0b04daa1227e2e74c2b2c
-F src/vdbeapi.c 1cb879087bb33db50e524c7c9385ca8f330384136a2880b0c15e39b2ac4b520f
+F src/vdbeapi.c 22a2661a2886f6b142fce91e95533a1841135e8217f65297d7df353a0eddf650
F src/vdbeaux.c dffcf79e7e415fcd6e4c8ac1ec7124cae5257018443adf09551c807655b04993
F src/vdbeblob.c 13f9287b55b6356b4b1845410382d6bede203ceb29ef69388a4a3d007ffacbe5
F src/vdbemem.c c936e9002af4993b84c4eb7133d6b1190efe46d391cc86117ecd67ba17b1a04b
@@ -816,7 +818,7 @@ F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867d
F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
F test/aggfault.test 777f269d0da5b0c2524c7ff6d99ae9a93db4f1b1839a914dd2a12e3035c29829
-F test/aggnested.test 2e738bfe2980df301a782f6e7bbf9459266f64f7e72f58f3b5c843bf897c568c
+F test/aggnested.test 7929208e173f5dbdbe8f67afbc59c07db99199d39ba5ce2d8a16be2c63600f53
F test/aggorderby.test e6b98dbbf3ababa96892435d387de2dcf602ef02c2b848d2d817473066f154ba
F test/alias.test 4529fbc152f190268a15f9384a5651bbbabc9d87
F test/all.test 2ecb8bbd52416642e41c9081182a8df05d42c75637afd4488aace78cc4b69e13
@@ -1648,7 +1650,7 @@ F test/temptable2.test 76821347810ecc88203e6ef0dd6897b6036ac788e9dd3e6b04fd4d163
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
F test/tester.tcl 68454ef88508c196d19e8694daa27bff7107a91857799eaa12f417188ae53ede
-F test/testrunner.tcl 14c8b8ece841b1dd17516a0dc9c7ad9b5f4d4db7987974d3fdf66ae56b2a71fa
+F test/testrunner.tcl 8a6721213bce1cfd3b33e1588cc6431143d96b98819206bf91f5a205fbb150d4
F test/testrunner_data.tcl 7f73f93634d32dafc857ed491b840f371113d09fde6a8bfb9e47b938d47b8c85
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
@@ -1983,7 +1985,7 @@ F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2
F test/win32lock.test e0924eb8daac02bf80e9da88930747bd44dd9b230b7759fed927b1655b467c9c
F test/win32longpath.test 4baffc3acb2e5188a5e3a895b2b543ed09e62f7c72d713c1feebf76222fe9976
F test/win32nolock.test ac4f08811a562e45a5755e661f45ca85892bdbbc
-F test/window1.test ccfeaf116afc6a8f748a8122a4f1ee6b69e6bbc5acee61197d3c17167338b100
+F test/window1.test 5e8abe56a7d667eeddbba6de180086dcf69ed528d046447a25464f945ece101f
F test/window2.tcl 492c125fa550cda1dd3555768a2303b3effbeceee215293adf8871efc25f1476
F test/window2.test e466a88bd626d66edc3d352d7d7e1d5531e0079b549ba44efb029d1fbff9fd3c
F test/window3.tcl acea6e86a4324a210fd608d06741010ca83ded9fde438341cb978c49928faf03
@@ -2060,13 +2062,13 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439
F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a1914b176
F tool/mkautoconfamal.sh cbdcf993fa83dccbef7fb77b39cdeb31ef9f77d9d88c9e343b58d35ca3898a6a
F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x
-F tool/mkctimec.tcl 372452e24267dfe1b496eec3992d10c6e5e7d7870a152560cdcfe5404bc8cc04 x
+F tool/mkctimec.tcl a16682eae5f01f85e5861b2aa215ca0d46b4230658ee25977e02b4508566fb75 x
F tool/mkkeywordhash.c b9faa0ae7e14e4dbbcd951cddd786bf46b8a65bb07b129ba8c0cfade723aaffd
F tool/mkmsvcmin.tcl 8897d515ef7f94772322db95a3b6fce6c614d84fe0bdd06ba5a1c786351d5a1d
F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61a07ef
F tool/mkopcodeh.tcl 769d9e6a8b462323150dc13a8539d6064664b72974f7894befe2491cc73e05cd
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
-F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d
+F tool/mkpragmatab.tcl 32e359ccb21011958a821955254bd7a5fa7915d01a8c16fed91ffc8b40cb4adf
F tool/mkshellc.tcl b7adf08b82de60811d2cb6af05ff59fc17e5cd6f3e98743c14eaaa3f8971fed0
F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
@@ -2107,7 +2109,7 @@ F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaa
F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848
F tool/src-verify.c 41c586dee84d0b190ad13e0282ed83d4a65ec9fefde9adf4943efdf6558eea7f
F tool/srcck1.c 371de5363b70154012955544f86fdee8f6e5326f
-F tool/srctree-check.tcl cef630bc4ff21a460d72479c43a42bf1c1ed61897659305c35c8d72e91bcb176
+F tool/srctree-check.tcl 9f1098d439159692ece45cc9e701b35df4956269399fe0c770e41f235d1ce5e6
F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
F tool/stripccomments.c 20b8aabc4694d0d4af5566e42da1f1a03aff057689370326e9269a9ddcffdc37
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
@@ -2140,8 +2142,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P f47a8d0a207a8442a7f621b070ce9dd1d6013ce26bcf68165d20bb379bd478a0 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301
-R 0b642bb03e1bb73ba395a556a197e4e8
+P a420a4f7ff76b0e9cf5f6d515ccfa31e526d58f4001a4015a367e2aa3c82091f 15b618e92a2708cc83256947736de8c494a9985a77e38bd5efc7e51e72cba344
+R 7bce1ab3b73fc9ca8b3b94f2470fc2d6
U drh
-Z 3c39b7a06f36c21b3f7d5adacbf5ab4c
+Z 13124fe09b7592eb801d594e79ae949a
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index 53fcd27c8e..71f504a420 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-a420a4f7ff76b0e9cf5f6d515ccfa31e526d58f4001a4015a367e2aa3c82091f
\ No newline at end of file
+b089bf46374b374d02d7654c65eb3e75d1777638b398061e47644af0dab48c9b
\ No newline at end of file
diff --git a/src/resolve.c b/src/resolve.c
index 0072f6b6aa..5f675c1d2d 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -1249,11 +1249,12 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){
while( pNC2
&& sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0
){
- pExpr->op2++;
+ pExpr->op2 += (1 + pNC2->nNestedSelect);
pNC2 = pNC2->pNext;
}
assert( pDef!=0 || IN_RENAME_OBJECT );
if( pNC2 && pDef ){
+ pExpr->op2 += pNC2->nNestedSelect;
assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg );
assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg );
testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 );
@@ -1812,6 +1813,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
/* Recursively resolve names in all subqueries in the FROM clause
*/
+ if( pOuterNC ) pOuterNC->nNestedSelect++;
for(i=0; ipSrc->nSrc; i++){
SrcItem *pItem = &p->pSrc->a[i];
if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){
@@ -1836,6 +1838,9 @@ static int resolveSelectStep(Walker *pWalker, Select *p){
}
}
}
+ if( pOuterNC && ALWAYS(pOuterNC->nNestedSelect>0) ){
+ pOuterNC->nNestedSelect--;
+ }
/* Set up the local name-context to pass to sqlite3ResolveExprNames() to
** resolve the result-set expression list.
diff --git a/src/select.c b/src/select.c
index 7a79385e0b..e97a5afb74 100644
--- a/src/select.c
+++ b/src/select.c
@@ -8472,7 +8472,7 @@ int sqlite3Select(
updateAccumulator(pParse, regAcc, pAggInfo, eDist);
if( eDist!=WHERE_DISTINCT_NOOP ){
struct AggInfo_func *pF = pAggInfo->aFunc;
- if( pF ){
+ if( ALWAYS(pF) ){
fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr);
}
}
diff --git a/src/shell.c.in b/src/shell.c.in
index ad16d72044..3cfe1a94a4 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -704,9 +704,8 @@ static void console_prepare_utf8(void){
conState.outCodePage = GetConsoleOutputCP();
if( in_console ){
SetConsoleCP(CP_UTF8);
- DWORD newConsoleMode = conState.consoleMode
- | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
- SetConsoleMode(conState.hConsole, newConsoleMode);
+ SetConsoleMode(conState.hConsole, conState.consoleMode
+ | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT);
console_utf8_in = 1;
}
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index dbf01dd139..23beb48de3 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -3397,6 +3397,7 @@ struct NameContext {
int nRef; /* Number of names resolved by this context */
int nNcErr; /* Number of errors encountered while resolving names */
int ncFlags; /* Zero or more NC_* flags defined below */
+ u32 nNestedSelect; /* Number of nested selects using this NC */
Select *pWinSelect; /* SELECT statement for any window functions */
};
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index 22952ba1a9..5ebd273831 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; inVar; i++){
diff --git a/test/aggnested.test b/test/aggnested.test
index 5f033a246c..933df06ce0 100644
--- a/test/aggnested.test
+++ b/test/aggnested.test
@@ -358,6 +358,60 @@ do_execsql_test 6.2.2 {
FROM t2 GROUP BY 'constant_string';
} {{}}
+#-------------------------------------------------------------------------
+reset_db
+
+do_execsql_test 7.0 {
+ CREATE TABLE invoice (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ amount DOUBLE PRECISION DEFAULT NULL,
+ name VARCHAR(100) DEFAULT NULL
+ );
+
+ INSERT INTO invoice (amount, name) VALUES
+ (4.0, 'Michael'), (15.0, 'Bara'), (4.0, 'Michael'), (6.0, 'John');
+}
+
+do_execsql_test 7.1 {
+ SELECT sum(amount), name
+ from invoice
+ group by name
+ having (select v > 6 from (select sum(amount) v) t)
+} {
+ 15.0 Bara
+ 8.0 Michael
+}
+
+do_execsql_test 7.2 {
+ SELECT (select 1 from (select sum(amount))) FROM invoice
+} {1}
+
+do_execsql_test 8.0 {
+ CREATE TABLE t1(x INT);
+ INSERT INTO t1 VALUES(100);
+ INSERT INTO t1 VALUES(20);
+ INSERT INTO t1 VALUES(3);
+ SELECT (SELECT y FROM (SELECT sum(x) AS y) AS t2 ) FROM t1;
+} {123}
+
+do_execsql_test 8.1 {
+ SELECT (
+ SELECT y FROM (
+ SELECT z AS y FROM (SELECT sum(x) AS z) AS t2
+ )
+ ) FROM t1;
+} {123}
+
+do_execsql_test 8.2 {
+ SELECT (
+ SELECT a FROM (
+ SELECT y AS a FROM (
+ SELECT z AS y FROM (SELECT sum(x) AS z) AS t2
+ )
+ )
+ ) FROM t1;
+} {123}
+
diff --git a/test/testrunner.tcl b/test/testrunner.tcl
index 1f1862ffdd..025f2ecdb4 100644
--- a/test/testrunner.tcl
+++ b/test/testrunner.tcl
@@ -425,7 +425,7 @@ for {set ii 0} {$ii < [llength $argv]} {incr ii} {
if {$isLast} { usage }
} elseif {($n>2 && [string match "$a*" --zipvfs]) || $a=="-z"} {
incr ii
- set TRG(zipvfs) [lindex $argv $ii]
+ set TRG(zipvfs) [file normalize [lindex $argv $ii]]
if {$isLast} { usage }
} else {
usage
diff --git a/test/window1.test b/test/window1.test
index c9bbae3ee0..d8348a8980 100644
--- a/test/window1.test
+++ b/test/window1.test
@@ -1881,7 +1881,7 @@ do_catchsql_test 57.3 {
SELECT max(y) OVER( ORDER BY (SELECT x FROM (SELECT sum(y) AS x FROM t1)))
)
FROM t3;
-} {1 {misuse of aggregate: sum()}}
+} {0 5}
# 2020-06-06 ticket 1f6f353b684fc708
reset_db
diff --git a/tool/mkctimec.tcl b/tool/mkctimec.tcl
index 098bf16e3b..9e425c0fdc 100755
--- a/tool/mkctimec.tcl
+++ b/tool/mkctimec.tcl
@@ -7,6 +7,13 @@
# definition used in src/ctime.c, run this script from
# the checkout root. It generates src/ctime.c .
#
+# Results are normally written into src/ctime.c. But if an argument is
+# provided, results are written there instead. Examples:
+#
+# tclsh tool/mkctimec.tcl ;# <-- results to src/ctime.c
+#
+# tclsh tool/mkctimec.tcl /dev/tty ;# <-- results to the terminal
+#
set ::headWarning {/* DO NOT EDIT!
@@ -429,10 +436,15 @@ foreach v $value2_options {
}]
}
-set ctime_c "src/ctime.c"
+if {$argc>0} {
+ set destfile [lindex $argv 0]
+} else {
+ set destfile "[file dir [file dir [file normal $argv0]]]/src/ctime.c"
+ puts "Overwriting $destfile..."
+}
-if {[catch {set cfd [open $ctime_c w]}]!=0} {
- puts stderr "File '$ctime_c' unwritable."
+if {[catch {set cfd [open $destfile w]}]!=0} {
+ puts stderr "File '$destfile' unwritable."
exit 1;
}
diff --git a/tool/mkpragmatab.tcl b/tool/mkpragmatab.tcl
index 5c36df0497..2b5b6fdcdb 100644
--- a/tool/mkpragmatab.tcl
+++ b/tool/mkpragmatab.tcl
@@ -9,6 +9,14 @@
# Then add the extra "case PragTyp_XXXXX:" and subsequent code for the
# new pragma in ../src/pragma.c.
#
+# The results are normally written into the ../src/pragma.h file. However,
+# if an alternative output file name is provided as an argument, then
+# results are written into the alternative. For example:
+#
+# tclsh tool/mkpragmatab.tcl ;# <--- Results to src/pragma.h
+#
+# tclsh tool/mkpragmatab.tcl /dev/tty ;# <-- results to terminal
+#
# Flag meanings:
set flagMeaning(NeedSchema) {Force schema load before running}
@@ -402,8 +410,12 @@ set pragma_def {
# Open the output file
#
-set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h"
-puts "Overwriting $destfile with new pragma table..."
+if {$argc>0} {
+ set destfile [lindex $argv 0]
+} else {
+ set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h"
+ puts "Overwriting $destfile with new pragma table..."
+}
set fd [open $destfile wb]
puts $fd {/* DO NOT EDIT!
** This file is automatically generated by the script at
diff --git a/tool/srctree-check.tcl b/tool/srctree-check.tcl
index 234e70fae9..f44e3ceec5 100644
--- a/tool/srctree-check.tcl
+++ b/tool/srctree-check.tcl
@@ -27,12 +27,15 @@ proc readfile {filename} {
# Find the root of the tree.
#
set ROOT [file dir [file dir [file normalize $argv0]]]
-cd $ROOT
# Name of the TCL interpreter
#
set TCLSH [info nameofexe]
+# Number of errors seen.
+#
+set NERR 0
+
######################### autoconf/tea/configure.ac ###########################
set confac [readfile $ROOT/autoconf/tea/configure.ac]
@@ -42,38 +45,46 @@ append pattern [string trim $vers]
append pattern {])}
if {[string first $pattern $confac]<=0} {
puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION"
- exit 1
+ puts "...... Fix: manually edit ./autoconf/tea/configure.ac to"
+ incr NERR
}
######################### autoconf/Makefile.msc ###############################
set f1 [readfile $ROOT/autoconf/Makefile.msc]
-exec mv $ROOT/autoconf/Makefile.msc $ROOT/autoconf/Makefile.msc.tmp
-exec $TCLSH $ROOT/tool/mkmsvcmin.tcl
-set f2 [readfile $ROOT/autoconf/Makefile.msc]
-exec mv $ROOT/autoconf/Makefile.msc.tmp $ROOT/autoconf/Makefile.msc
+exec $TCLSH $ROOT/tool/mkmsvcmin.tcl $ROOT/Makefile.msc tmp1.txt
+set f2 [readfile tmp1.txt]
+file delete tmp1.txt
if {$f1 != $f2} {
puts "ERROR: ./autoconf/Makefile.msc does not agree with ./Makefile.msc"
+ puts "...... Fix: tclsh tool/mkmsvcmin.tcl"
+ incr NERR
}
######################### src/pragma.h ########################################
set f1 [readfile $ROOT/src/pragma.h]
-exec mv $ROOT/src/pragma.h $ROOT/src/pragma.h.tmp
-exec $TCLSH $ROOT/tool/mkpragmatab.tcl
-set f2 [readfile $ROOT/src/pragma.h]
-exec mv $ROOT/src/pragma.h.tmp $ROOT/src/pragma.h
+exec $TCLSH $ROOT/tool/mkpragmatab.tcl tmp2.txt
+set f2 [readfile tmp2.txt]
+file delete tmp2.txt
if {$f1 != $f2} {
puts "ERROR: ./src/pragma.h does not agree with ./tool/mkpragmatab.tcl"
+ puts "...... Fix: tclsh tool/mkpragmatab.tcl"
+ incr NERR
}
######################### src/ctime.c ########################################
set f1 [readfile $ROOT/src/ctime.c]
-exec mv $ROOT/src/ctime.c $ROOT/src/ctime.c.tmp
-exec $TCLSH $ROOT/tool/mkctimec.tcl
-set f2 [readfile $ROOT/src/ctime.c]
-exec mv $ROOT/src/ctime.c.tmp $ROOT/src/ctime.c
+exec $TCLSH $ROOT/tool/mkctimec.tcl tmp3.txt
+set f2 [readfile tmp3.txt]
+file delete tmp3.txt
if {$f1 != $f2} {
puts "ERROR: ./src/ctime.c does not agree with ./tool/mkctimec.tcl"
+ puts "..... Fix: tclsh tool/mkctimec.tcl"
+ incr NERR
}
+
+# If any errors are seen, exit 1 so that the build will fail.
+#
+if {$NERR>0} {exit 1}