]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
More docs and cleanups related to the aggregate UDF state. Correct the OOM check...
authorstephan <stephan@noemail.net>
Fri, 28 Jul 2023 01:51:14 +0000 (01:51 +0000)
committerstephan <stephan@noemail.net>
Fri, 28 Jul 2023 01:51:14 +0000 (01:51 +0000)
FossilOrigin-Name: ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c

ext/jni/src/c/sqlite3-jni.c
ext/jni/src/org/sqlite/jni/SQLFunction.java
ext/jni/src/org/sqlite/jni/sqlite3_context.java
manifest
manifest.uuid

index fba90108d1ef6723f3291e53684548f4a8acdb99..6303d87553ee071e1eeaf302ea74b557dfb920c9 100644 (file)
@@ -404,6 +404,33 @@ static void s3jni_free(void * p){
   if(p) sqlite3_free(p);
 }
 
+
+/*
+** This function is NOT part of the sqlite3 public API. It is strictly
+** for use by the sqlite project's own Java/JNI bindings.
+**
+** For purposes of certain hand-crafted JNI function bindings, we
+** need a way of reporting errors which is consistent with the rest of
+** the C API, as opposed to throwing JS exceptions. To that end, this
+** internal-use-only function is a thin proxy around
+** sqlite3ErrorWithMessage(). The intent is that it only be used from
+** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
+** from client code.
+**
+** Returns err_code.
+*/
+static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){
+  if( db!=0 ){
+    if( 0!=zMsg ){
+      const int nMsg = sqlite3Strlen30(zMsg);
+      sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+    }else{
+      sqlite3ErrorWithMsg(db, err_code, NULL);
+    }
+  }
+  return err_code;
+}
+
 /**
    Clears s's state, releasing any Java references. Before doing so,
    it calls s's xDestroy() method, ignoring the lack of that method or
@@ -724,13 +751,17 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam
 
    isFinal must be 1 for xFinal() calls and 0 for all others.
 
-   Returns 0 on succes, SQLITE_NOMEM on allocation error.
+   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 s3jni_setAggregateContext(JNIEnv * env, jobject jCx,
-                                sqlite3_context * pCx,
-                                int isFinal){
+static int udf_setAggregateContext(JNIEnv * env, jobject jCx,
+                                   sqlite3_context * pCx,
+                                   int isFinal){
   jmethodID setter;
   void * pAgg;
+  int rc = 0;
   struct NphCacheLine * const cacheLine =
     S3Global_nph_cache(env, ClassNames.sqlite3_context);
   if(cacheLine && cacheLine->klazz && cacheLine->midSetAgg){
@@ -746,43 +777,24 @@ static int s3jni_setAggregateContext(JNIEnv * env, jobject jCx,
       cacheLine->midSetAgg = setter;
     }
   }
-  pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 8);
-  if( pAgg ){
+  pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4);
+  if( pAgg || isFinal ){
     (*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg);
-    IFTHREW_REPORT;
-  }
-  return pAgg ? (int)0 : SQLITE_NOMEM;
-}
-
-
-/*
-** This function is NOT part of the sqlite3 public API. It is strictly
-** for use by the sqlite project's own Java/JNI bindings.
-**
-** For purposes of certain hand-crafted JNI function bindings, we
-** need a way of reporting errors which is consistent with the rest of
-** the C API, as opposed to throwing JS exceptions. To that end, this
-** internal-use-only function is a thin proxy around
-** sqlite3ErrorWithMessage(). The intent is that it only be used from
-** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not
-** from client code.
-**
-** Returns err_code.
-*/
-static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){
-  if( db!=0 ){
-    if( 0!=zMsg ){
-      const int nMsg = sqlite3Strlen30(zMsg);
-      sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
-    }else{
-      sqlite3ErrorWithMsg(db, err_code, NULL);
+    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.");
     }
+  }else{
+    assert(!pAgg);
+    rc = SQLITE_NOMEM;
   }
-  return err_code;
+  return rc;
 }
 
-
-
 /* Sets a native int32 value in OutputPointer.Int32 object ppOut. */
 static void setOutputInt32(JNIEnv * env, jobject ppOut, int v){
   jmethodID setter = 0;
@@ -1161,7 +1173,7 @@ static int udf_xFSI(sqlite3_context* pCx, int argc,
   if(rc) return rc;
   //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
   if( UDF_SCALAR != s->type ){
-    rc = s3jni_setAggregateContext(env, args.jcx, pCx, 0);
+    rc = udf_setAggregateContext(env, args.jcx, pCx, 0);
   }
   if( 0 == rc ){
     (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv);
@@ -1187,7 +1199,7 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s,
   }
   //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType));
   if( UDF_SCALAR != s->type ){
-    rc = s3jni_setAggregateContext(env, jcx, cx, 1);
+    rc = udf_setAggregateContext(env, jcx, cx, 1);
   }
   if( 0 == rc ){
     (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx);
index 7e7d81750436406b72058d7d6ab6a63f2fb6703a..eaa83df9a96335986b8b6714b617d8d1f5bf32c7 100644 (file)
@@ -18,42 +18,41 @@ package org.sqlite.jni;
    sqlite3_create_function() JNI-bound API to give that native code
    access to the callback functions needed in order to implement SQL
    functions in Java. This class is not used by itself: see the
-   three inner classes.
-
-   Note that if a given function is called multiple times in a single
-   SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., then the
-   context object passed to each one will be different. This is most
-   significant for aggregates and window functions, since they must
-   assign their results to the proper context.
-
-   TODO: add helper APIs to map sqlite3_context instances to
-   func-specific state and to clear that when the aggregate or window
-   function is done.
+   inner classes Scalar, Aggregate<T>, and Window<T>.
 */
 public abstract class SQLFunction {
 
   /**
      ContextMap is a helper for use with aggregate and window
      functions, to help them manage their accumulator state across
-     calls to xStep() and xFinal(). It works by mapping
+     calls to the UDF's callbacks.
+
+     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 (if needed) via xFinal(). This class takes care of
+     such mappings.
+
+     This class works by mapping
      sqlite3_context::getAggregateContext() to a single piece of state
-     which persists across a set of 0 or more SQLFunction.xStep()
-     calls and 1 SQLFunction.xFinal() call.
-   */
+     which persists across a "matching set" of the UDF's callbacks.
+  */
   public static final class ContextMap<T> {
     private java.util.Map<Long,ValueHolder<T>> map
-      = new java.util.HashMap<Long,ValueHolder<T>>();
+      = new java.util.HashMap<>();
 
     /**
-       Should be called from a UDF's xStep() method, 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, else
-       an existing one is preferred.  It returns a ValueHolder which
-       can be used to modify that state directly without having to put
-       a new result back in the underlying 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 an existing one is
+       use and the 2nd argument is ignored.  It returns a ValueHolder
+       which can be used to modify that state directly without
+       requiring that the user update the underlying map.
     */
-    public ValueHolder<T> xStep(sqlite3_context cx, T initialValue){
+    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<T>(initialValue));
@@ -63,13 +62,13 @@ public abstract class SQLFunction {
 
     /**
        Should be called from a UDF's xFinal() method and passed that
-       method's first argument. This function returns the value
-       associated with cx.getAggregateContext(), or null if
-       this.xStep() has not been called to set up such a mapping. That
-       will be the case if an aggregate is used in a statement which
-       has no result rows.
+       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 not been
+       called to set up such a mapping. That will be the case if an
+       aggregate is used in a statement which has no result rows.
     */
-    public T xFinal(sqlite3_context cx){
+    public T takeAggregateState(sqlite3_context cx){
       final ValueHolder<T> h = map.remove(cx.getAggregateContext());
       return null==h ? null : h.value;
     }
@@ -79,43 +78,47 @@ public abstract class SQLFunction {
   public static abstract class Scalar extends SQLFunction {
     public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
     /**
-       Optionally override to be notified when the function is
-       finalized by SQLite.
+       Optionally override to be notified when the UDF is finalized by
+       SQLite.
     */
     public void xDestroy() {}
   }
 
-  //! Subclass for creating aggregate functions.
+  /**
+     SQLFunction Subclass for creating aggregate functions.  Its T is
+     the data type of its "accumulator" state, an instance of which is
+     intended to be be managed using the getAggregateState() and
+     takeAggregateState() methods.
+  */
   public static abstract class Aggregate<T> extends SQLFunction {
     public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
     public abstract void xFinal(sqlite3_context cx);
+
+    //! See Scalar.xDestroy()
     public void xDestroy() {}
 
     private final ContextMap<T> map = new ContextMap<>();
 
-    /**
-       See ContextMap<T>.xStep().
-    */
-    public final ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
-      return map.xStep(cx, initialValue);
+    //! See ContextMap<T>.getAggregateState().
+    protected final ValueHolder<T> getAggregateState(sqlite3_context cx, T initialValue){
+      return map.getAggregateState(cx, initialValue);
     }
 
-    /**
-       See ContextMap<T>.xFinal().
-    */
-    public final T takeAggregateState(sqlite3_context cx){
-      return map.xFinal(cx);
+    //! See ContextMap<T>.takeAggregateState().
+    protected final T takeAggregateState(sqlite3_context cx){
+      return map.takeAggregateState(cx);
     }
   }
 
-  //! Subclass for creating window functions.
+  /**
+     An SQLFunction subclass for creating window functions.  Note that
+     Window<T> inherits from Aggregate<T> and each instance is
+     required to implemenat the inherited abstract methods from that
+     class. See Aggregate<T> for information on managing the call
+     state across matching calls of the UDF callbacks.
+  */
   public static abstract class Window<T> extends Aggregate<T> {
-    public Window(){
-      super();
-    }
-    //public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
     public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
-    //public abstract void xFinal(sqlite3_context cx);
     public abstract void xValue(sqlite3_context cx);
   }
 }
index d6bc3012a19f706eacf3117b70e7a189cb6ccccb..bf2224dd5e9287fbf68b359bce060ceecfba5baf 100644 (file)
@@ -34,11 +34,10 @@ public class sqlite3_context extends NativePointerHolder<sqlite3_context> {
   /**
      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. That value will be the same across matching calls to the
-     xStep() and xFinal() routines, as well as xValue() and xInverse()
-     in window UDFs. 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().
+     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;
index 058fbe8d0901ac157e3bee21dfb870bb5e231462..62375125713cbbe7d8882773fb17bf3560fb4fcf 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\san\sOOM\scheck\sto\sthe\sprevious\scheck-in.\sMinor\sinternal\sAPI\srenaming.
-D 2023-07-28T01:19:44.606
+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
 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 7e76652684c38df7c48ac3300056601202b0c45d91c5f3671725e17c3c69ec6a
+F ext/jni/src/c/sqlite3-jni.c 9464d7f186c52cecd4c6ac91d3da35f29fd98923a048befc8d2d872edd639a41
 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 d77e0a4bb6bc0d65339aeacd6b20fc7e3b8a05f899c1f0ead90dda61f0a01522
+F ext/jni/src/org/sqlite/jni/SQLFunction.java b176c46828a52084dd3a39e5084d0b0ce12dcaf2abe719a58f4d1d92733e1136
 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/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 4a0b22226705a4f89d9c8093e0f51a8991cc0464864120970c915695afbba4e2
+F ext/jni/src/org/sqlite/jni/sqlite3_context.java 4e7eebc8a5c85ecfbae3aa2c4ddb7f1ca861c218d3829d31afe16f6b11104213
 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 640574984741c7a9472d7f8be7bce87e736d7947ce673ae4a25008d74238ad90
-R ce71c2fd0629446f06e751d7b64d5f7d
+P 6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce
+R 0fe909577d9a504bc5127b45fc11fe45
 U stephan
-Z 3429cf1394e9a8e9214fcef9821d7359
+Z 8f663d2013372069850b9c169f30fdc3
 # Remove this line to create a well-formed Fossil manifest.
index b7049ce4d7c5e0c6f01407239d9bd3ad9f7053dc..28aa247cb8506a8f7653596c495800b14c91496e 100644 (file)
@@ -1 +1 @@
-6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce
\ No newline at end of file
+ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c
\ No newline at end of file