mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-30 19:03:16 +03:00
Do not pre-allocate sqlite3_aggregate_context() for Java UDFs, as it unduly complicates UDF initialization.
FossilOrigin-Name: e8308f0c6ec2d8999c8a2502fb130cb3501ba326f23f71f2cd8d452debae79b5
This commit is contained in:
@ -1164,49 +1164,6 @@ static int S3JniAutoExtension_init(JNIEnv *const env,
|
||||
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
|
||||
@ -1628,6 +1585,7 @@ static int udf_report_exception(JNIEnv * const env, int translateToErr,
|
||||
(*env)->ExceptionDescribe( env );
|
||||
S3JniExceptionClear;
|
||||
}
|
||||
UNREF_L(ex);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -1645,10 +1603,6 @@ static int udf_xFSI(sqlite3_context* const pCx, int argc,
|
||||
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{
|
||||
@ -1678,15 +1632,10 @@ static int udf_xFV(sqlite3_context* cx, S3JniUdf * s,
|
||||
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;
|
||||
@ -1834,6 +1783,20 @@ WRAP_INT_SVALUE(1value_1type, sqlite3_value_type)
|
||||
#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){
|
||||
|
@ -771,6 +771,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init
|
||||
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
|
||||
|
@ -55,6 +55,9 @@ public abstract class SQLFunction {
|
||||
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
|
||||
@ -64,20 +67,20 @@ public abstract class SQLFunction {
|
||||
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.
|
||||
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>T must be of a type which can be legally stored as a value in
|
||||
java.util.HashMap<KeyType,T>.
|
||||
<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;
|
||||
}
|
||||
@ -92,7 +95,7 @@ public abstract class SQLFunction {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -126,19 +126,19 @@ public final class SQLite3Jni {
|
||||
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
|
||||
@ -151,22 +151,42 @@ public final class SQLite3Jni {
|
||||
// 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);
|
||||
|
||||
|
@ -723,7 +723,9 @@ public class Tester1 implements Runnable {
|
||||
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){
|
||||
@ -740,15 +742,19 @@ public class Tester1 implements Runnable {
|
||||
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);
|
||||
@ -767,9 +773,9 @@ public class Tester1 implements Runnable {
|
||||
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);
|
||||
|
@ -18,10 +18,7 @@ package org.sqlite.jni;
|
||||
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
|
||||
@ -32,19 +29,29 @@ public final class sqlite3_context extends NativePointerHolder<sqlite3_context>
|
||||
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
|
||||
@ -52,14 +59,18 @@ public final class sqlite3_context extends NativePointerHolder<sqlite3_context>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user