dir.doc := $(dir.jni)/doc
doc: $(JAVA_FILES.main)
- $(bin.javadoc) -cp $(classpath) -d $(dir.doc) org.sqlite.jni
+ $(bin.javadoc) -cp $(classpath) -d $(dir.doc) -quiet org.sqlite.jni
########################################################################
# Clean up...
return 0;
}
-/*
-** Requires that jCx be a Java-side sqlite3_context wrapper for pCx.
-** This function calls sqlite3_aggregate_context() to allocate a tiny
-** sliver of memory, the address of which is set in
-** jCx->aggregateContext. The memory is only used as a key for
-** mapping client-side results of aggregate result sets across
-** calls to the UDF's callbacks.
-**
-** isFinal must be 1 for xFinal() calls and 0 for all others, the
-** difference being that the xFinal() invocation will not allocate
-** new memory if it was not already, resulting in a value of 0
-** for jCx->aggregateContext.
-**
-** Returns 0 on success. Returns SQLITE_NOMEM on allocation error,
-** noting that it will not allocate when isFinal is true. It returns
-** SQLITE_ERROR if there's a serious internal error in dealing with
-** the JNI state.
-*/
-static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
- sqlite3_context * pCx,
- int isFinal){
- void * pAgg;
- int rc = 0;
- S3JniNphClass * const pNC =
- S3JniGlobal_nph_cache(env, &S3NphRefs.sqlite3_context);
- if( !pNC->fidAggCtx ){
- S3JniMutex_Nph_enter;
- if( !pNC->fidAggCtx ){
- pNC->fidAggCtx = (*env)->GetFieldID(env, pNC->klazz, "aggregateContext", "J");
- EXCEPTION_IS_FATAL("Cannot get sqlite3_contex.aggregateContext member.");
- }
- S3JniMutex_Nph_leave;
- }
- pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : sizeof(void*));
- if( pAgg || isFinal ){
- (*env)->SetLongField(env, jCx, pNC->fidAggCtx, (jlong)pAgg);
- }else{
- assert(!pAgg);
- rc = SQLITE_NOMEM;
- }
- return rc;
-}
-
/*
** Common init for OutputPointer_set_Int32() and friends. pRef must be
** a pointer from S3NphRefs. jOut must be an instance of that
(*env)->ExceptionDescribe( env );
S3JniExceptionClear;
}
+ UNREF_L(ex);
return rc;
}
int rc = udf_args(env, pCx, argc, argv, &args.jcx, &args.jargv);
//MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
- if( rc ) return rc;
- if( UDF_SCALAR != s->type ){
- rc = udf_setAggregateContext(env, args.jcx, pCx, 0);
- }
if( 0 == rc ){
(*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
S3JniIfThrew{
return SQLITE_NOMEM;
}
//MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
- if( UDF_SCALAR != s->type ){
- rc = udf_setAggregateContext(env, jcx, cx, isFinal);
- }
- if( 0 == rc ){
- (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
- S3JniIfThrew{
- rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
- zFuncType);
- }
+ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
+ S3JniIfThrew{
+ rc = udf_report_exception(env, isFinal, cx, s->zFuncName,
+ zFuncType);
}
UNREF_L(jcx);
return rc;
#undef WRAP_MUTF8_VOID
#undef WRAP_STR_STMT_INT
+
+S3JniApi(sqlite3_aggregate_context(),jlong,1aggregate_1context)(
+ JniArgsEnvClass, jobject jCx, jboolean initialize
+){
+ sqlite3_context * const pCx = PtrGet_sqlite3_context(jCx);
+ void * const p = pCx
+ ? sqlite3_aggregate_context(pCx, (int)(initialize
+ ? (int)sizeof(void*)
+ : 0))
+ : 0;
+ return (jlong)p / sizeof(void*);
+}
+
+
/* Central auto-extension handler. */
static int s3jni_run_java_auto_extensions(sqlite3 *pDb, const char **pzErr,
const struct sqlite3_api_routines *ignored){
JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv
(JNIEnv *, jclass);
+/*
+ * Class: org_sqlite_jni_SQLite3Jni
+ * Method: sqlite3_aggregate_context
+ * Signature: (Lorg/sqlite/jni/sqlite3_context;Z)J
+ */
+JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1aggregate_1context
+ (JNIEnv *, jclass, jobject, jboolean);
+
/*
* Class: org_sqlite_jni_SQLite3Jni
* Method: sqlite3_auto_extension
Client UDFs are free to perform such mappings using custom
approaches. The provided Aggregate<T> and Window<T> classes
use this.
+
+ <p>T must be of a type which can be legally stored as a value in
+ java.util.HashMap<KeyType,T>.
*/
public static final class PerContextState<T> {
private final java.util.Map<Long,ValueHolder<T>> map
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 cx.getAggregateContext() within the map, one is
- created using the given initial value, else the existing one is
- used and the 2nd argument is ignored. It returns a
- ValueHolder<T> which can be used to modify that state directly
- without requiring that the client update the underlying map's
- entry.
-
- <p>T must be of a type which can be legally stored as a value in
- java.util.HashMap<KeyType,T>.
+ mapping for the given context within the map, one is created
+ using the given initial value, else the existing one is used
+ and the 2nd argument is ignored. It returns a ValueHolder<T>
+ which can be used to modify that state directly without
+ requiring that the client update the underlying map's entry.
+
+ <p>The caller is obligated to eventually call
+ takeAggregateState() to clear the mapping.
*/
public ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
- ValueHolder<T> rc = map.get(cx.getAggregateContext());
- if(null == rc){
- map.put(cx.getAggregateContext(), rc = new ValueHolder<>(initialValue));
+ final Long key = cx.getAggregateContext(true);
+ ValueHolder<T> rc = null==key ? null : map.get(key);
+ if( null==rc ){
+ map.put(key, rc = new ValueHolder<>(initialValue));
}
return rc;
}
rows.
*/
public T takeAggregateState(sqlite3_context cx){
- final ValueHolder<T> h = map.remove(cx.getAggregateContext());
+ final ValueHolder<T> h = map.remove(cx.getAggregateContext(false));
return null==h ? null : h.value;
}
}
This will clean up any cached per-JNIEnv info. Calling into the
library will re-initialize the cache on demand.
- This process does not close any databases or finalize
+ <p>This process does not close any databases or finalize
any prepared statements because their ownership does not depend on
a given thread. For proper library behavior, and to
avoid C-side leaks, be sure to finalize all statements and close
all databases before calling this function.
- Calling this from the main application thread is not strictly
+ <p>Calling this from the main application thread is not strictly
required but is "polite." Additional threads must call this
before ending or they will leak cache entries in the C heap,
which in turn may keep numerous Java-side global references
active.
- This routine returns false without side effects if the current
+ <p>This routine returns false without side effects if the current
JNIEnv is not cached, else returns true, but this information is
primarily for testing of the JNI bindings and is not information
which client-level code should use to make any informed
// alphabetized. The SQLITE_... values. on the other hand, are
// grouped by category.
+ /**
+ Functions exactly like the native form except that (A) the
+ returned value is only intended for use as a lookup key in a
+ higher-level data structure and (B) the 2nd argument is a boolean
+ instead of an int. If passed true, it will attempt to allocate
+ enough memory to use as a UDF-call-local context key. If passed
+ false it will not allocate any memory.
+
+ <p>It is only valid for the life of the current UDF method call
+ and must not be retained for later use. The return value 0
+ indicates an allocation error unless initialize is false, in
+ which case it means that the given context was never passed to
+ this function with a true second argument so never had to
+ allocate.
+
+ <p>For the JNI wrapping, the value of sz is provided for API
+ consistency but it is ignored unless it's 0. Results are
+ undefined if the value is negative.
+ */
+ public static native long sqlite3_aggregate_context(sqlite3_context cx, boolean initialize);
/**
Functions almost as documented for the C API, with these
exceptions:
- - The callback interface is is shorter because of cross-language
- differences. Specifically, 3rd argument to the C auto-extension
- callback interface is unnecessary here.
+ <p>- The callback interface is is shorter because of
+ cross-language differences. Specifically, 3rd argument to the C
+ auto-extension callback interface is unnecessary here.
- The C API docs do not specifically say so, if the list of
+ <p>The C API docs do not specifically say so, but if the list of
auto-extensions is manipulated from an auto-extension, it is
undefined which, if any, auto-extensions will subsequently
execute for the current database.
- See the AutoExtension class docs for more information.
+ <p>See the AutoExtension class docs for more information.
*/
public static native int sqlite3_auto_extension(@NotNull AutoExtension callback);
SQLFunction func = new SQLFunction.Aggregate<Integer>(){
@Override
public void xStep(sqlite3_context cx, sqlite3_value[] args){
- this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]);
+ final ValueHolder<Integer> agg = this.getAggregateState(cx, 0);
+ agg.value += sqlite3_value_int(args[0]);
+ affirm( agg == this.getAggregateState(cx, 0) );
}
@Override
public void xFinal(sqlite3_context cx){
int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
affirm(0 == rc);
sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t");
+ affirm( null != stmt );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
- final int v = sqlite3_column_int(stmt, 0);
+ int v = sqlite3_column_int(stmt, 0);
affirm( 6 == v );
+ int v2 = sqlite3_column_int(stmt, 1);
+ affirm( 30+v == v2 );
++n;
}
+ affirm( 1==n );
affirm(!xFinalNull.value);
sqlite3_reset(stmt);
- // Ensure that the accumulator is reset...
+ // Ensure that the accumulator is reset on subsequent calls...
n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
final int v = sqlite3_column_int(stmt, 0);
affirm( 6 == c0 );
affirm( 12 == c1 );
}
+ sqlite3_finalize(stmt);
affirm( 1 == n );
affirm(!xFinalNull.value);
- sqlite3_finalize(stmt);
execSql(db, "SELECT myfunc(1) WHERE 0");
affirm(xFinalNull.value);
SQL functions (a.k.a. UDFs).
*/
public final class sqlite3_context extends NativePointerHolder<sqlite3_context> {
- /**
- Only set by the JNI layer.
- */
- private long aggregateContext = 0;
+ private Long aggregateContext = null;
/**
getAggregateContext() corresponds to C's
such that all calls into those callbacks can determine which "set"
of those calls they belong to.
- If this object is being used in the context of an aggregate or
+ <p>If the argument is true and the aggregate context has not yet
+ been set up, it will be initialized fetched on demand, else it
+ won't. The intent is that xStep(), xValue(), and xInverse()
+ methods pass true and xFinal() methods pass false.
+
+ <p>This function treats numeric 0 as null, always returning null instead
+ of 0.
+
+ <p>If this object is being used in the context of an aggregate or
window UDF, this function returns a non-0 value which is distinct
for each set of UDF callbacks from a single invocation of the
UDF, otherwise it returns 0. The returned value is only only
valid within the context of execution of a single SQL statement,
- and may be re-used by future invocations of the UDF in different
- SQL statements.
+ and must not be re-used by future invocations of the UDF in
+ different SQL statements.
- Consider this SQL, where MYFUNC is a user-defined aggregate function:
+ <p>Consider this SQL, where MYFUNC is a user-defined aggregate function:
+ {code
SELECT MYFUNC(A), MYFUNC(B) FROM T;
+ }
- The xStep() and xFinal() methods of the callback need to be able
+ <p>The xStep() and xFinal() methods of the callback need to be able
to differentiate between those two invocations in order to
perform their work properly. The value returned by
getAggregateContext() will be distinct for each of those
key for mapping callback invocations to whatever client-defined
state is needed by the UDF.
- There is one case where this will return 0 in the context of an
+ <p>There is one case where this will return 0 in the context of an
aggregate or window function: if the result set has no rows,
the UDF's xFinal() will be called without any other x...() members
having been called. In that one case, no aggregate context key will
have been generated. xFinal() implementations need to be prepared to
accept that condition as legal.
*/
- public long getAggregateContext(){
- return aggregateContext;
+ public synchronized Long getAggregateContext(boolean initIfNeeded){
+ if( aggregateContext==null ){
+ aggregateContext = SQLite3Jni.sqlite3_aggregate_context(this, initIfNeeded);
+ if( !initIfNeeded && null==aggregateContext ) aggregateContext = 0L;
+ }
+ return (null==aggregateContext || 0!=aggregateContext) ? aggregateContext : null;
}
}
-C Add\sdoc/testrunner.md,\sfor\sdocumenting\sthe\stestrunner.tcl\sscript.
-D 2023-08-24T19:08:50.024
+C Do\snot\spre-allocate\ssqlite3_aggregate_context()\sfor\sJava\sUDFs,\sas\sit\sunduly\scomplicates\sUDF\sinitialization.
+D 2023-08-24T21:31:56.676
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
-F ext/jni/GNUmakefile 6b3c0fd8d055c129735702d0b288589d25544dd404a00d46d9eb43770fe7f78f
+F ext/jni/GNUmakefile 6aeafa0ebcf0f0d834c814ae8b450b54135ea11a2a7868f90b6286ec1bf6020f
F ext/jni/README.md 9d3caa2e038bfe5e8356a9e8ff66f93ca0647ac278339eeea296f10017f5cf35
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
-F ext/jni/src/c/sqlite3-jni.c 8db2fcc05dd7749f9f4175e2654344feccac6abfd19fd9db0d116c6350e3b625
-F ext/jni/src/c/sqlite3-jni.h 2b81cfb83933cb18e5f690487f4556591d3329538809c847d00190aa4d69aa1d
+F ext/jni/src/c/sqlite3-jni.c 0d98ab3b117893904a06f0f8a350d68d4e911939b6aee4f0eb1ef707502ac23c
+F ext/jni/src/c/sqlite3-jni.h 7e9f36434b919cd8b6aa66c61e3910e9f112e252f52d1ac8a9811c52710aefcb
F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892
F ext/jni/src/org/sqlite/jni/AutoExtension.java bcc1849b2fccbe5e2d7ac9e9ac7f8d05a6d7088a8fedbaad90e39569745a61e6
F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc
F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86
F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
-F ext/jni/src/org/sqlite/jni/SQLFunction.java 5851698d96ee29171d68930ad758d0f5a253f7575f1feb890d82b2557a8d3ef5
+F ext/jni/src/org/sqlite/jni/SQLFunction.java 4d6291fa14fcca1a040609378f9f00a193145d79c3abbda98ba32c340904cbeb
F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java d96f10a097c1d614b44353e85a65368d9aca565d5ae57fae0104811594fbdfba
-F ext/jni/src/org/sqlite/jni/Tester1.java 76f308ad9bf0bd74374561c30c65564ed24583a465264b751d9e2333980149f1
+F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 866b320e6cb089ebe96c14763575df9e2e0d5e52a0ec87a52119ff17ec59ffbf
+F ext/jni/src/org/sqlite/jni/Tester1.java 239eb0133547d4a23317a6dd5bc456172cfb1f2547fd15ba8408871c2776a721
F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629
F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d
F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d
F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b
F ext/jni/src/org/sqlite/jni/package-info.java 1a547913d681411d65c5fe0bca840f049abe5612740154a125545ea9e2481747
F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc
-F ext/jni/src/org/sqlite/jni/sqlite3_context.java fe7797a696978f057528a57b7a11e7797ed41fd7afcf100c5ebb67055d9f706f
+F ext/jni/src/org/sqlite/jni/sqlite3_context.java 42df6769ab9c8de40d24f39f49152cdaa6e0c06d1468660a95dfee9ecaeb9a63
F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc
F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a
F ext/jni/src/org/sqlite/jni/tester/SQLTester.java bc3d6797a2f6cb7d443a0b72af84e5a45e0416b96af52e432d28e123db1970c3
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 62b404d62fd62f4d220838b59c9f38a71afa2d4a8c3af0a5c9495fa7020972cf
-R a33be9d18a7bfec02bf745dca74a3ae4
-U dan
-Z b231c489ac6e506e7affd58c17184d19
+P 9c69a28401c7273823f2c2b291fd417febeb278afb9ce085a4b944505ca13d23
+R 1c78bd3d04007fccae2264dff957535e
+U stephan
+Z 6a74bde3490bcf9bf06f9dd5ecb09697
# Remove this line to create a well-formed Fossil manifest.
-9c69a28401c7273823f2c2b291fd417febeb278afb9ce085a4b944505ca13d23
\ No newline at end of file
+e8308f0c6ec2d8999c8a2502fb130cb3501ba326f23f71f2cd8d452debae79b5
\ No newline at end of file