More Java docs about making use of the aggregate context. Change the JNI mapping to set the sqlite3_context::aggregateContext member directly, instead of via a superflous setter, because that way is faster.

FossilOrigin-Name: 7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45
This commit is contained in:
stephan 2023-07-28 09:25:05 +00:00
parent 75d3b1b5a2
commit 09c2640fe3
6 changed files with 84 additions and 60 deletions

View File

@ -275,7 +275,7 @@ struct NphCacheLine {
jmethodID midSet /* setNativePointer() */;
jmethodID midGet /* getNativePointer() */;
jmethodID midCtor /* constructor */;
jmethodID midSetAgg /* sqlite3_context::setAggregateContext() */;
jfieldID fidSetAgg /* sqlite3_context::aggregateContext */;
};
typedef struct JNIEnvCacheLine JNIEnvCacheLine;
@ -745,11 +745,14 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam
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->setAggregateContext(). The memory is only used as a key for
mapping, client-side, results of aggregate result sets across
xStep() and xFinal() methods.
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.
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
@ -759,35 +762,34 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam
static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
sqlite3_context * pCx,
int isFinal){
jmethodID setter;
jfieldID member;
void * pAgg;
int rc = 0;
struct NphCacheLine * const cacheLine =
S3Global_nph_cache(env, ClassNames.sqlite3_context);
if(cacheLine && cacheLine->klazz && cacheLine->midSetAgg){
setter = cacheLine->midSetAgg;
assert(setter);
if(cacheLine && cacheLine->klazz && cacheLine->fidSetAgg){
member = cacheLine->fidSetAgg;
assert(member);
}else{
jclass const klazz =
cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, jCx);
setter = (*env)->GetMethodID(env, klazz, "setAggregateContext", "(J)V");
member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J");
if( !member ){
IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; }
return s3jni_db_error(sqlite3_context_db_handle(pCx),
SQLITE_ERROR,
"Internal error: cannot find "
"sqlite3_context::aggregateContext field.");
}
if(cacheLine){
assert(cacheLine->klazz);
assert(!cacheLine->midSetAgg);
cacheLine->midSetAgg = setter;
assert(!cacheLine->fidSetAgg);
cacheLine->fidSetAgg = member;
}
}
pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4);
if( pAgg || isFinal ){
(*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg);
IFTHREW {
EXCEPTION_REPORT;
EXCEPTION_CLEAR/*arguable, but so is propagation*/;
rc = s3jni_db_error(sqlite3_context_db_handle(pCx),
SQLITE_ERROR,
"sqlite3_context::setAggregateContext() "
"unexpectedly threw.");
}
(*env)->SetLongField(env, jCx, member, (jlong)pAgg);
}else{
assert(!pAgg);
rc = SQLITE_NOMEM;

View File

@ -35,11 +35,17 @@ public abstract class SQLFunction {
such mappings.
This class works by mapping
sqlite3_context::getAggregateContext() to a single piece of state
which persists across a "matching set" of the UDF's callbacks.
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.
*/
public static final class ContextMap<T> {
private java.util.Map<Long,ValueHolder<T>> map
private final java.util.Map<Long,ValueHolder<T>> map
= new java.util.HashMap<>();
/**

View File

@ -656,7 +656,6 @@ public class Tester1 {
}
private static void testBusy(){
outln("testBusy()...");
final String dbName = "_busy-handler.db";
final sqlite3 db1 = new sqlite3();
final sqlite3 db2 = new sqlite3();

View File

@ -15,39 +15,56 @@ package org.sqlite.jni;
/**
sqlite3_context instances are used in conjunction with user-defined
SQL functions (a.k.a. UDFs). They are opaque pointers.
The getAggregateContext() method corresponds to C's
sqlite3_aggregate_context(), with a slightly different interface in
order to account for cross-language differences. It serves the same
purposes in a slightly different way: it provides a key which is
stable across invocations of UDF xStep() and xFinal() pairs, to
which a UDF may map state across such calls (e.g. a numeric result
which is being accumulated).
SQL functions (a.k.a. UDFs).
*/
public class sqlite3_context extends NativePointerHolder<sqlite3_context> {
public sqlite3_context() {
super();
}
private long aggcx = 0;
/**
If this object is being used in the context of an aggregate or
window UDF, the UDF binding layer will set a unique context value
here, else this will return 0. That value will be the same across
matching calls to the UDF callbacks. This value can be used as a
key to map state which needs to persist across such calls, noting
that such state should be cleaned up via xFinal().
*/
public long getAggregateContext(){
return aggcx;
}
/**
For use only by the JNI layer. It's permitted to call this even
For use only by the JNI layer. It's permitted to set this even
though it's private.
*/
private void setAggregateContext(long n){
aggcx = n;
private long aggregateContext = 0;
/**
getAggregateContext() corresponds to C's
sqlite3_aggregate_context(), with a slightly different interface
to account for cross-language differences. It serves the same
purposes in a slightly different way: it provides a key which is
stable across invocations of "matching sets" of a UDF's callbacks,
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
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.
Consider this SQL, where MYFUNC is a user-defined aggregate function:
SELECT MYFUNC(A), MYFUNC(B) FROM T;
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
invocations of MYFUNC() and is intended to be used as a lookup
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
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;
}
}

View File

@ -1,5 +1,5 @@
C More\sdocs\sand\scleanups\srelated\sto\sthe\saggregate\sUDF\sstate.\sCorrect\sthe\sOOM\scheck\sto\sbehave\sproperly\sif\sxFinal()\sis\scalled\swithout\sa\smatching\sxStep(),\sxValue(),\sor\sxInverse().
D 2023-07-28T01:51:14.668
C More\sJava\sdocs\sabout\smaking\suse\sof\sthe\saggregate\scontext.\sChange\sthe\sJNI\smapping\sto\sset\sthe\ssqlite3_context::aggregateContext\smember\sdirectly,\sinstead\sof\svia\sa\ssuperflous\ssetter,\sbecause\sthat\sway\sis\sfaster.
D 2023-07-28T09:25:05.029
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -232,20 +232,20 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d
F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623
F ext/jni/src/c/sqlite3-jni.c 9464d7f186c52cecd4c6ac91d3da35f29fd98923a048befc8d2d872edd639a41
F ext/jni/src/c/sqlite3-jni.c 9d0d58f3633bd8f467f893f45548873ed2c5451c673b0782b3cc6bfa92327b10
F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506
F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c
F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 70dc7bc41f80352ff3d4331e2e24f45fcd23353b3641e2f68a81bd8262215861
F ext/jni/src/org/sqlite/jni/OutputPointer.java 08a752b58a33696c5eaf0eb9361a0966b188dec40f4a3613eb133123951f6c5f
F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5a1d7b2607eb2ef596fcf4492a49d1b3a5bdea3af9918e11716831ffd2f02284
F ext/jni/src/org/sqlite/jni/SQLFunction.java b176c46828a52084dd3a39e5084d0b0ce12dcaf2abe719a58f4d1d92733e1136
F ext/jni/src/org/sqlite/jni/SQLFunction.java 268291ee7be1406b13a3b220df2eac59b9337473d5eb9fa40bd528eefb57252c
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610
F ext/jni/src/org/sqlite/jni/Tester1.java 2334d1dd0efc22179654c586065c77d904830d736059b4049f9cd9e6832565bd
F ext/jni/src/org/sqlite/jni/Tester1.java 7d8742eb6d6aba429171b2ba6136f4f17569a280676d846cbe319fa95a97ae4d
F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1
F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee
F ext/jni/src/org/sqlite/jni/sqlite3.java c7d0500c7269882243aafb41425928d094b2fcbdbc2fd1caffc276871cd3fae3
F ext/jni/src/org/sqlite/jni/sqlite3_context.java 4e7eebc8a5c85ecfbae3aa2c4ddb7f1ca861c218d3829d31afe16f6b11104213
F ext/jni/src/org/sqlite/jni/sqlite3_context.java 841ac0384ec23e7d24ad9a928f8728b98bd3c4c3814d401200c6531786b9c241
F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 3193693440071998a66870544d1d2314f144bea397ce4c3f83ff225d587067a0
F ext/jni/src/org/sqlite/jni/sqlite3_value.java f9d8c0766b1d1b290564cb35db8d37be54c42adc8df22ee77b8d39e3e93398cd
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
@ -2067,8 +2067,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 6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce
R 0fe909577d9a504bc5127b45fc11fe45
P ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c
R ce373f0682789ebfcc8ec45219320dda
U stephan
Z 8f663d2013372069850b9c169f30fdc3
Z 02ac0e260dba2cb9492d0ae9287f8a5f
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c
7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45