]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add more JNI docs, tests, and a handful of Java-side overloads.
authorstephan <stephan@noemail.net>
Thu, 24 Aug 2023 11:57:51 +0000 (11:57 +0000)
committerstephan <stephan@noemail.net>
Thu, 24 Aug 2023 11:57:51 +0000 (11:57 +0000)
FossilOrigin-Name: d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0

ext/jni/GNUmakefile
ext/jni/README.md
ext/jni/jar-dist.make
ext/jni/src/c/sqlite3-jni.c
ext/jni/src/c/sqlite3-jni.h
ext/jni/src/org/sqlite/jni/AutoExtension.java
ext/jni/src/org/sqlite/jni/SQLite3Jni.java
ext/jni/src/org/sqlite/jni/Tester1.java
ext/jni/src/org/sqlite/jni/TesterFts5.java
manifest
manifest.uuid

index cc728003d78a39b180059c7833d8c682b230d069..6b32c40a824459f6d024ceca8cb97d92bac94d14 100644 (file)
@@ -223,19 +223,18 @@ $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
 $(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
 $(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
        $(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
-                       $(sqlite3-jni.c) -shared -o $@ -lpthread
+                       $(sqlite3-jni.c) -shared -o $@
 all: $(sqlite3-jni.dll)
 
 .PHONY: test
-test.flags ?= -v
+test.flags ?=
+test.main.flags = -ea -Djava.library.path=$(dir.bld.c) \
+                  $(java.flags) -cp $(classpath) \
+                  org.sqlite.jni.Tester1
 test: $(SQLite3Jni.class) $(sqlite3-jni.dll)
-       $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
-               $(java.flags) -cp $(classpath) \
-               org.sqlite.jni.Tester1 $(test.flags)
-test-mt:  $(SQLite3Jni.class) $(sqlite3-jni.dll)
-       $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
-               $(java.flags) -cp $(classpath) \
-               org.sqlite.jni.Tester1 -t 7 -r 50 -shuffle $(test.flags)
+       $(bin.java) $(test.main.flags) $(test.flags)
+       @echo "Again in multi-threaded mode:"
+       $(bin.java) $(test.main.flags) -t 5 -r 20 -shuffle $(test.flags)
 
 tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test))
 tester.flags ?= # --verbose
@@ -271,7 +270,7 @@ endif
 
 tester-ext: tester-local
 tester: tester-ext
-tests: test test-mt tester
+tests: test tester
 package.jar.in := $(abspath $(dir.src)/jar.in)
 CLEAN_FILES += $(package.jar.in)
 $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main)
index 90067556d2fb4e4cd1012ac5d18a0d1cc9738b4d..6e5e07cc0393f06f4952a309593bfa9fc8bd5364 100644 (file)
@@ -16,7 +16,7 @@ Technical support is available in the forum:
 > **FOREWARNING:** this subproject is very much in development and
   subject to any number of changes. Please do not rely on any
   information about its API until this disclaimer is removed.  The JNI
-  bindgins released with version 3.43 are a "tech preview" and 3.44
+  bindings released with version 3.43 are a "tech preview" and 3.44
   will be "final," at which point strong backward compatibility
   guarantees will apply.
 
@@ -43,29 +43,34 @@ Non-goals:
 - Creation of high-level OO wrapper APIs. Clients are free to create
   them off of the C-style API.
 
+
 Hello World
 -----------------------------------------------------------------------
 
 ```java
 import org.sqlite.jni.*;
-import static org.sqlite.jni.SQLite3Jni;
+import static SQLite3Jni.*;
+
 ...
-OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
-int rc = sqlite3_open(":memory:", out);
-final sqlite3 db = out.take();
-if( 0 != rc ){
-  if( null != db ){
-    System.out.print("Error opening db: "+sqlite3_errmsg(db));
-    sqlite3_close(db);
-  }else{
-    System.out.print("Error opening db: rc="+rc);
+
+final sqlite3 db = sqlite3_open(":memory:");
+try {
+  final int rc = sqlite3_errcode(db);
+  if( 0 != rc ){
+    if( null != db ){
+      System.out.print("Error opening db: "+sqlite3_errmsg(db));
+    }else{
+      System.out.print("Error opening db: rc="+rc);
+    }
+    ... handle error ...
   }
-  ... handle error ...
+  // ... else use the db ...
+}finally{
+  // ALWAYS close databases using sqlite3_close() or sqlite3_close_v2()
+  // when done with them. All of their active statement handles must
+  // first have been passed to sqlite3_finalize().
+  sqlite3_close_v2(db);
 }
-
-... use db ...
-
-sqlite3_close_v2(db);
 ```
 
 Building
@@ -86,28 +91,90 @@ $ make test
 $ make clean
 ```
 
-The jar distribution can be created with `make jar`.
+The jar distribution can be created with `make jar`, but note that it
+does not contain the binary DLL file. A different DLL is needed for
+each target platform.
+
 
 <a id='1to1ish'></a>
 One-to-One(-ish) Mapping to C
 ========================================================================
 
 This JNI binding aims to provide as close to a 1-to-1 experience with
-the C API as cross-language semantics allow. Changes are necessarily
-made where cross-language semantics do not allow a 1-to-1, and
-judiciously made where a 1-to-1 mapping would be unduly cumbersome to
-use in Java.
+the C API as cross-language semantics allow. Interface changes are
+necessarily made where cross-language semantics do not allow a 1-to-1,
+and judiciously made where a 1-to-1 mapping would be unduly cumbersome
+to use in Java. In all cases, this binding makes every effort to
+provide semantics compatible with the C API documentation even if the
+interface to those semantics is slightly different.  Any cases which
+deviate from those semantics (either removing or adding semantics) are
+clearly documented.
+
+Where it makes sense to do so for usability, Java-side overloads are
+provided which accept or return data in alternative forms or provide
+sensible default argument values. In all such cases they are thin
+proxies around the corresponding C APIs and do not introduce new
+semantics.
+
+In some very few cases, Java-specific capabilities have been added in
+new APIs, all of which have "_java" somewhere in their names.
+Examples include:
+
+- `sqlite3_result_java_object()`
+- `sqlite3_column_java_object()`
+- `sqlite3_column_java_casted()`
+- `sqlite3_value_java_object()`
+- `sqlite3_value_java_casted()`
+
+which, as one might surmise, collectively enable the passing of
+arbitrary Java objects from user-defined SQL functions through to the
+caller.
+
+
+Golden Rule: Garbage Collection Cannot Free SQLite Resources
+------------------------------------------------------------------------
+
+It is important that all databases and prepared statement handles get
+cleaned up by client code. A database cannot be closed if it has open
+statement handles. `sqlite3_close()` fails if the db cannot be closed
+whereas `sqlite3_close_v2()` recognizes that case and marks the db as
+a "zombie," pending finalization when the library detects that all
+pending statements have been closed. Be aware that Java garbage
+collection _cannot_ close a database or finalize a prepared statement.
+Those things require explicit API calls.
+
 
-Golden Rule: _Never_ Throw from Callbacks (Unless...)
+Golden Rule #2: _Never_ Throw from Callbacks (Unless...)
 ------------------------------------------------------------------------
 
+All routines in this API, barring explicitly documented exceptions,
+retain C-like semantics. For example, they are not permitted to throw
+or propagate exceptions and must return error information (if any) via
+result codes or `null`. The only cases where the C-style APIs may
+throw is through client-side misuse, e.g. passing in a null where it
+shouldn't be used. The APIs clearly mark function parameters which
+should not be null, but does not actively defend itself against such
+misuse. Some C-style APIs explicitly accept `null` as a no-op for
+usability's sake, and some of the JNI APIs deliberately return an
+error code, instead of segfaulting, when passed a `null`.
+
 Client-defined callbacks _must never throw exceptions_ unless _very
 explicitly documented_ as being throw-safe. Exceptions are generally
 reserved for higher-level bindings which are constructed to
 specifically deal with them and ensure that they do not leak C-level
-resources. In some cases, callback handlers (see below) are permitted
-to throw, in which cases they get translated to C-level result codes
-and/or messages.
+resources. In some cases, callback handlers are permitted to throw, in
+which cases they get translated to C-level result codes and/or
+messages. If a callback which is not permitted to throw throws, its
+exception may trigger debug output but will otherwise be suppressed.
+
+The reason some callbacks are permitted to throw and others not is
+because all such callbacks act as proxies for C function callback
+interfaces and some of those interfaces have no error-reporting
+mechanism. Those which are capable of propagating errors back through
+the library convert exceptions from callbacks into corresponding
+C-level error information. Those which cannot propagate errors
+necessarily suppress any exceptions in order to maintain the C-style
+semantics of the APIs.
 
 
 Awkward Callback Names
@@ -246,4 +313,5 @@ in-flux nature of this API.
 
 Various APIs which accept callbacks, e.g. `sqlite3_trace_v2()` and
 `sqlite3_update_hook()`, use interfaces similar to those shown above.
-
+Despite the changes in signature, the JNI layer makes every effort to
+provide the same semantics as the C API documentation suggests.
index ac1a768b8c1a168ff039aa8649b57723226e7950..7596c99f3f8aa2be92abf5317beb4293ffc89857 100644 (file)
@@ -46,11 +46,13 @@ $(sqlite3-jni.dll):
        echo  "*** to configure it for your system.                                 ***"; \
        echo  "************************************************************************"
        $(CC) $(CFLAGS) $(SQLITE_OPT) \
-               src/sqlite3-jni.c -lpthread -shared -o $@
+               src/sqlite3-jni.c -shared -o $@
        @echo "Now try running it with: make test"
 
+test.flags = -Djava.library.path=. sqlite3-jni-*.jar
 test: $(sqlite3-jni.dll)
-       java -jar -Djava.library.path=. sqlite3-jni-*.jar
+       java -jar $(test.flags)
+       java -jar $(test.flags) -t 7 -r 10 -shuffle
 
 clean:
        -rm -f $(sqlite3-jni.dll)
index debb221d6afc1345052b6c6c2881601c3458728b..03ba8be57af28db2b9898b2776fbb4ab2f248785 100644 (file)
@@ -1429,7 +1429,7 @@ typedef struct {
 } ResultJavaVal;
 
 /* For use with sqlite3_result/value_pointer() */
-#define ResultJavaValuePtrStr "ResultJavaVal"
+#define ResultJavaValuePtrStr "org.sqlite.jni.ResultJavaVal"
 
 /*
 ** Allocate a new ResultJavaVal and assign it a new global ref of
@@ -1915,15 +1915,10 @@ JDECL(jint,1auto_1extension)(JENV_CSELF, jobject jAutoExt){
 
 JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt,
                         jint ndx, jbyteArray baData, jint nMax){
-  int rc;
-  if(!baData){
-    rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx);
-  }else{
-    jbyte * const pBuf = JBA_TOC(baData);
-    rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax,
-                           SQLITE_TRANSIENT);
-    JBA_RELEASE(baData,pBuf);
-  }
+  jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
+  int const rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+                                   pBuf, (int)nMax, SQLITE_TRANSIENT);
+  JBA_RELEASE(baData,pBuf);
   return (jint)rc;
 }
 
@@ -1960,15 +1955,21 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName
 
 JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt,
                        jint ndx, jbyteArray baData, jint nMax){
-  if(baData){
-    jbyte * const pBuf = JBA_TOC(baData);
-    int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf,
-                               (int)nMax, SQLITE_TRANSIENT);
-    JBA_RELEASE(baData, pBuf);
-    return (jint)rc;
-  }else{
-    return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx);
-  }
+  jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
+  int const rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+                                   (const char *)pBuf,
+                                   (int)nMax, SQLITE_TRANSIENT);
+  JBA_RELEASE(baData, pBuf);
+  return (jint)rc;
+}
+
+JDECL(jint,1bind_1text16)(JENV_CSELF, jobject jpStmt,
+                          jint ndx, jbyteArray baData, jint nMax){
+  jbyte * const pBuf = baData ? JBA_TOC(baData) : 0;
+  int const rc = sqlite3_bind_text16(PtrGet_sqlite3_stmt(jpStmt), (int)ndx,
+                                     pBuf, (int)nMax, SQLITE_TRANSIENT);
+  JBA_RELEASE(baData, pBuf);
+  return (jint)rc;
 }
 
 JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt,
@@ -3595,18 +3596,19 @@ JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){
 }
 
 static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){
-  int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal));
+  sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal);
+  int const nLen = sqlite3_value_bytes16(sv);
   jbyteArray jba;
   const jbyte * pBytes;
   switch(mode){
     case SQLITE_UTF16:
-      pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal));
+      pBytes = sqlite3_value_text16(sv);
       break;
     case SQLITE_UTF16LE:
-      pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal));
+      pBytes = sqlite3_value_text16le(sv);
       break;
     case SQLITE_UTF16BE:
-      pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal));
+      pBytes = sqlite3_value_text16be(sv);
       break;
     default:
       assert(!"not possible");
index 86fada95749276ad71f364afa8b060380f64cfe5..8052a6027ef003fa32f20a3250582aff00976582 100644 (file)
@@ -843,6 +843,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1
 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text
   (JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
 
+/*
+ * Class:     org_sqlite_jni_SQLite3Jni
+ * Method:    sqlite3_bind_text16
+ * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I
+ */
+JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text16
+  (JNIEnv *, jclass, jobject, jint, jbyteArray, jint);
+
 /*
  * Class:     org_sqlite_jni_SQLite3Jni
  * Method:    sqlite3_bind_zeroblob
index fcad273d3dac9e72fad08527e5ef7ce54ff12568..443345fde4f7d59faa6376c8ac1f99daa12963a7 100644 (file)
 package org.sqlite.jni;
 
 /**
-   A callback for use with sqlite3_auto_extension().
+   A callback for use with the sqlite3_auto_extension() family of
+   APIs.
 */
 public interface AutoExtension {
   /**
      Must function as described for a sqlite3_auto_extension()
-     callback, with the caveat that the signature is more limited.
+     callback, with the caveat that the signature is shorter.
 
-     As an exception (as it were) to the callbacks-must-not-throw
-     rule, AutoExtensions may throw and the exception's error message
+     AutoExtensions may throw and the exception's error message
      will be set as the db's error string.
 
-     Hints for implementations:
+     Tips for implementations:
 
      - Opening a database from an auto-extension handler will lead to
        an endless recursion of the auto-handler triggering itself
        indirectly for each newly-opened database.
 
-     - If this routine is stateful, it is a good idea to make the
+     - If this routine is stateful, it may be useful to make the
        overridden method synchronized.
 
      - Results are undefined if db is closed by an auto-extension.
index 29ed0e560276c60d470793ea9d9b520e4bb8cfb3..ef67890918a8bcc20536e557f2eb932327f3be78 100644 (file)
@@ -154,32 +154,35 @@ public final class SQLite3Jni {
      Functions almost as documented for the C API, with these
      exceptions:
 
-     - The callback interface is more limited because of
-       cross-language differences. Specifically, auto-extensions do
-       not have access to the sqlite3_api object which native
-       auto-extensions do.
+     - The callback interface is is shorter because of cross-language
+       differences. Specifically, 3rd argument to the C auto-extension
+       callback interface is unnecessary here.
 
-     - 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 (it depends
-       on multiple factors).
+
+     The C API docs do not specifically say so, 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.
   */
   public static native int sqlite3_auto_extension(@NotNull AutoExtension callback);
 
+  /**
+     Results are undefined if data is not null and n<0 || n>=data.length.
+  */
+  public static native int sqlite3_bind_blob(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
+  );
+
   public static int sqlite3_bind_blob(
     @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
   ){
-    return (null == data)
+    return (null==data)
       ? sqlite3_bind_null(stmt, ndx)
       : sqlite3_bind_blob(stmt, ndx, data, data.length);
   }
 
-  private static native int sqlite3_bind_blob(
-    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n
-  );
-
   public static native int sqlite3_bind_double(
     @NotNull sqlite3_stmt stmt, int ndx, double v
   );
@@ -200,13 +203,10 @@ public final class SQLite3Jni {
     @NotNull sqlite3_stmt stmt
   );
 
-
   /**
-     A level of indirection required to ensure that the input to the
-     C-level function of the same name is a NUL-terminated UTF-8
-     string.
+     Requires that paramName be a NUL-terminated UTF-8 string.
   */
-  private static native int sqlite3_bind_parameter_index(
+  public static native int sqlite3_bind_parameter_index(
     @NotNull sqlite3_stmt stmt, byte[] paramName
   );
 
@@ -218,14 +218,22 @@ public final class SQLite3Jni {
   }
 
   /**
-     Works like the C-level sqlite3_bind_text() but (A) assumes
-     SQLITE_TRANSIENT for the final parameter and (B) behaves like
-     sqlite3_bind_null() if the data argument is null.
+     Works like the C-level sqlite3_bind_text() but assumes
+     SQLITE_TRANSIENT for the final C API parameter.
+
+     Results are undefined if data is not null and
+     maxBytes>=data.length. If maxBytes is negative then results are
+     undefined if data is not null and does not contain a NUL byte.
   */
   private static native int sqlite3_bind_text(
     @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
   );
 
+  /**
+     Converts data, if not null, to a UTF-8-encoded byte array and
+     binds it as such, returning the result of the C-level
+     sqlite3_bind_null() or sqlite3_bind_text().
+  */
   public static int sqlite3_bind_text(
     @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
   ){
@@ -234,6 +242,9 @@ public final class SQLite3Jni {
     return sqlite3_bind_text(stmt, ndx, utf8, utf8.length);
   }
 
+  /**
+     Requires that data be null or in UTF-8 encoding.
+  */
   public static int sqlite3_bind_text(
     @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
   ){
@@ -242,6 +253,41 @@ public final class SQLite3Jni {
       : sqlite3_bind_text(stmt, ndx, data, data.length);
   }
 
+  /**
+     Identical to the sqlite3_bind_text() overload with the same
+     signature but requires that its input be encoded in UTF-16 in
+     platform byte order.
+  */
+  private static native int sqlite3_bind_text16(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes
+  );
+
+  /**
+     Converts its string argument to UTF-16 and binds it as such, returning
+     the result of the C-side function of the same name. The 3rd argument
+     may be null.
+  */
+  public static int sqlite3_bind_text16(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data
+  ){
+    if(null == data) return sqlite3_bind_null(stmt, ndx);
+    final byte[] bytes = data.getBytes(StandardCharsets.UTF_16);
+    return sqlite3_bind_text16(stmt, ndx, bytes, bytes.length);
+  }
+
+  /**
+     Requires that data be null or in UTF-16 encoding in platform byte
+     order. Returns the result of the C-level sqlite3_bind_null() or
+     sqlite3_bind_text().
+  */
+  public static int sqlite3_bind_text16(
+    @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data
+  ){
+    return (null == data)
+      ? sqlite3_bind_null(stmt, ndx)
+      : sqlite3_bind_text16(stmt, ndx, data, data.length);
+  }
+
   public static native int sqlite3_bind_zeroblob(
     @NotNull sqlite3_stmt stmt, int ndx, int n
   );
@@ -253,8 +299,7 @@ public final class SQLite3Jni {
   /**
      As for the C-level function of the same name, with a BusyHandler
      instance in place of a callback function. Pass it a null handler
-     to clear the busy handler. Calling this multiple times with the
-     same object is a no-op on the second and subsequent calls.
+     to clear the busy handler.
   */
   public static native int sqlite3_busy_handler(
     @NotNull sqlite3 db, @Nullable BusyHandler handler
@@ -595,15 +640,41 @@ public final class SQLite3Jni {
     @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb
   );
 
+  /**
+     Convenience overload which returns its db handle directly. The returned
+     object might not have been successfully opened: use sqlite3_errcode() to
+     check whether it is in an error state.
+
+     Ownership of the returned value is passed to the caller, who must eventually
+     pass it to sqlite3_close() or sqlite3_close_v2().
+  */
+  public static sqlite3 sqlite3_open(@Nullable String filename){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    sqlite3_open(filename, out);
+    return out.take();
+  };
+
   public static native int sqlite3_open_v2(
     @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb,
     int flags, @Nullable String zVfs
   );
 
+  /**
+     Has the same semantics as the sqlite3-returning sqlite3_open()
+     but uses sqlite3_open_v2() instead of sqlite3_open().
+  */
+  public static sqlite3 sqlite3_open_v2(@Nullable String filename, int flags,
+                                        @Nullable String zVfs){
+    final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
+    sqlite3_open_v2(filename, out, flags, zVfs);
+    return out.take();
+  };
+
   /**
      The sqlite3_prepare() family of functions require slightly
-     different signatures than their native counterparts, but
-     overloading allows us to install several convenience forms.
+     different signatures than their native counterparts, but (A) they
+     retain functionally equivalent semantics and (B) overloading
+     allows us to install several convenience forms.
 
      All of them which take their SQL in the form of a byte[] require
      that it be in UTF-8 encoding unless explicitly noted otherwise.
@@ -648,6 +719,26 @@ public final class SQLite3Jni {
     return sqlite3_prepare(db, utf8, utf8.length, outStmt, null);
   }
 
+  /**
+     Convenience overload which returns its statement handle directly,
+     or null on error or when reading only whitespace or
+     comments. sqlite3_errcode() can be used to determine whether
+     there was an error or the input was empty. Ownership of the
+     returned object is passed to the caller, who must eventually pass
+     it to sqlite3_finalize().
+  */
+  public static sqlite3_stmt sqlite3_prepare(
+    @NotNull sqlite3 db, @NotNull String sql
+  ){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    sqlite3_prepare(db, sql, out);
+    return out.take();
+  }
+
+  /**
+     See sqlite3_prepare() for details about the slight API differences
+     from the C API.
+  */
   private static native int sqlite3_prepare_v2(
     @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
     @NotNull OutputPointer.sqlite3_stmt outStmt,
@@ -677,6 +768,18 @@ public final class SQLite3Jni {
     return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null);
   }
 
+  /**
+     Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+     but uses sqlite3_prepare_v2().
+  */
+  public static sqlite3_stmt sqlite3_prepare_v2(
+    @NotNull sqlite3 db, @NotNull String sql
+  ){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    sqlite3_prepare_v2(db, sql, out);
+    return out.take();
+  }
+
   private static native int sqlite3_prepare_v3(
     @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes,
     int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt,
@@ -706,6 +809,18 @@ public final class SQLite3Jni {
     return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null);
   }
 
+  /**
+     Works identically to the sqlite3_stmt-returning sqlite3_prepare()
+     but uses sqlite3_prepare_v3().
+  */
+  public static sqlite3_stmt sqlite3_prepare_v3(
+    @NotNull sqlite3 db, @NotNull String sql, int prepFlags
+  ){
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    sqlite3_prepare_v3(db, sql, prepFlags, out);
+    return out.take();
+  }
+
   /**
      If the C API was built with SQLITE_ENABLE_PREUPDATE_HOOK defined, this
      acts as a proxy for C's sqlite3_preupdate_blobwrite(), else it returns
index c655ae7ffd19cd8b3799c5930d250632dd2e944f..bafc73bf685b315e644320f473669bc052b199d1 100644 (file)
@@ -21,6 +21,16 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
+/**
+   An annotation for Tester1 tests which we do not want to run in
+   reflection-driven test mode because either they are not suitable
+   for multi-threaded threaded mode or we have to control their execution
+   order.
+*/
+@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
+@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
+@interface ManualTest{}
+
 public class Tester1 implements Runnable {
   //! True when running in multi-threaded mode.
   private static boolean mtMode = false;
@@ -30,6 +40,10 @@ public class Tester1 implements Runnable {
   private static boolean shuffle = false;
   //! True to dump the list of to-run tests to stdout.
   private static boolean listRunTests = false;
+  //! True to squelch all out() and outln() output.
+  private static boolean quietMode = false;
+  //! Total number of runTests() calls.
+  private static int nTestRuns = 0;
   //! List of test*() methods to run.
   private static List<java.lang.reflect.Method> testMethods = null;
   //! List of exceptions collected by run()
@@ -48,27 +62,37 @@ public class Tester1 implements Runnable {
   static final Metrics metrics = new Metrics();
 
   public synchronized static void outln(){
-    System.out.println("");
+    if( !quietMode ){
+      System.out.println("");
+    }
   }
 
   public synchronized static void outln(Object val){
-    System.out.print(Thread.currentThread().getName()+": ");
-    System.out.println(val);
+    if( !quietMode ){
+      System.out.print(Thread.currentThread().getName()+": ");
+      System.out.println(val);
+    }
   }
 
   public synchronized static void out(Object val){
-    System.out.print(val);
+    if( !quietMode ){
+      System.out.print(val);
+    }
   }
 
   @SuppressWarnings("unchecked")
   public synchronized static void out(Object... vals){
-    System.out.print(Thread.currentThread().getName()+": ");
-    for(Object v : vals) out(v);
+    if( !quietMode ){
+      System.out.print(Thread.currentThread().getName()+": ");
+      for(Object v : vals) out(v);
+    }
   }
 
   @SuppressWarnings("unchecked")
   public synchronized static void outln(Object... vals){
-    out(vals); out("\n");
+    if( !quietMode ){
+      out(vals); out("\n");
+    }
   }
 
   static volatile int affirmCount = 0;
@@ -85,6 +109,7 @@ public class Tester1 implements Runnable {
     affirm(v, "Affirmation failed.");
   }
 
+  @ManualTest /* because testing this for threading is pointless */
   private void test1(){
     affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
     affirm(SQLITE_MAX_LENGTH > 0);
@@ -280,6 +305,16 @@ public class Tester1 implements Runnable {
     affirm(0 != stmt.getNativePointer());
     sqlite3_finalize(stmt);
     affirm(0 == stmt.getNativePointer() );
+
+    affirm( 0==sqlite3_errcode(db) );
+    stmt = sqlite3_prepare(db, "intentional error");
+    affirm( null==stmt );
+    affirm( 0!=sqlite3_errcode(db) );
+    affirm( 0==sqlite3_errmsg(db).indexOf("near \"intentional\"") );
+    sqlite3_finalize(stmt);
+    stmt = sqlite3_prepare(db, "/* empty input*/\n-- comments only");
+    affirm( null==stmt );
+    affirm( 0==sqlite3_errcode(db) );
     sqlite3_close_v2(db);
   }
 
@@ -383,8 +418,11 @@ public class Tester1 implements Runnable {
     sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);");
     String[] list1 = { "hell🤩", "w😃rld", "!" };
     int rc;
+    int n = 0;
     for( String e : list1 ){
-      rc = sqlite3_bind_text(stmt, 1, e);
+      rc = (0==n)
+        ? sqlite3_bind_text(stmt, 1, e)
+        : sqlite3_bind_text16(stmt, 1, e);
       affirm(0 == rc);
       rc = sqlite3_step(stmt);
       affirm(SQLITE_DONE==rc);
@@ -393,7 +431,7 @@ public class Tester1 implements Runnable {
     sqlite3_finalize(stmt);
     stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;");
     StringBuilder sbuf = new StringBuilder();
-    int n = 0;
+    n = 0;
     while( SQLITE_ROW == sqlite3_step(stmt) ){
       String txt = sqlite3_column_text16(stmt, 0);
       //outln("txt = "+txt);
@@ -521,6 +559,7 @@ public class Tester1 implements Runnable {
     affirm(xDestroyCalled.value);
   }
 
+  @ManualTest /* because threading is meaningless here */
   private void testToUtf8(){
     /**
        https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
@@ -873,6 +912,7 @@ public class Tester1 implements Runnable {
     affirm( 7 == counter.value );
   }
 
+  @ManualTest /* because threads inherently break this test */
   private void testBusy(){
     final String dbName = "_busy-handler.db";
     final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
@@ -1156,6 +1196,8 @@ public class Tester1 implements Runnable {
      it throws.
   */
   @SuppressWarnings("unchecked")
+  @ManualTest /* because the Fts5 parts are not yet known to be
+                 thread-safe */
   private void testFts5() throws Exception {
     if( !sqlite3_compileoption_used("ENABLE_FTS5") ){
       //outln("SQLITE_ENABLE_FTS5 is not set. Skipping FTS5 tests.");
@@ -1206,6 +1248,8 @@ public class Tester1 implements Runnable {
     sqlite3_close(db);
   }
 
+  @ManualTest/* because multiple threads legitimately make these
+                results unpredictable */
   private synchronized void testAutoExtension(){
     final ValueHolder<Integer> val = new ValueHolder<>(0);
     final ValueHolder<String> toss = new ValueHolder<>(null);
@@ -1296,6 +1340,7 @@ public class Tester1 implements Runnable {
     affirm( 8 == val.value );
   }
 
+  @ManualTest /* because we only want to run this test manually */
   private void testSleep(){
     out("Sleeping briefly... ");
     sqlite3_sleep(600);
@@ -1308,6 +1353,7 @@ public class Tester1 implements Runnable {
     }
   }
 
+  @ManualTest /* because we only want to run this test on demand */
   private void testFail(){
     affirm( false, "Intentional failure." );
   }
@@ -1355,6 +1401,9 @@ public class Tester1 implements Runnable {
         testFts5();
       }
     }
+    synchronized( this.getClass() ){
+      ++nTestRuns;
+    }
   }
 
   public void run() {
@@ -1375,6 +1424,8 @@ public class Tester1 implements Runnable {
 
      CLI flags:
 
+     -q|-quiet: disables most test output.
+
      -t|-thread N: runs the tests in N threads
       concurrently. Default=1.
 
@@ -1400,6 +1451,7 @@ public class Tester1 implements Runnable {
     Integer nRepeat = 1;
     boolean forceFail = false;
     boolean sqlLog = false;
+    boolean squelchTestOutput = false;
     for( int i = 0; i < args.length; ){
       String arg = args[i++];
       if(arg.startsWith("-")){
@@ -1421,6 +1473,8 @@ public class Tester1 implements Runnable {
           sqlLog = true;
         }else if(arg.equals("naps")){
           takeNaps = true;
+        }else if(arg.equals("q") || arg.equals("quiet")){
+          squelchTestOutput = true;
         }else{
           throw new IllegalArgumentException("Unhandled flag:"+arg);
         }
@@ -1430,19 +1484,16 @@ public class Tester1 implements Runnable {
     {
       // Build list of tests to run from the methods named test*().
       testMethods = new ArrayList<>();
-      final List<String> excludes = new ArrayList<>();
-      // Tests we want to control the order of:
-      if( !forceFail ) excludes.add("testFail");
-      excludes.add("test1");
-      excludes.add("testAutoExtension");
-      excludes.add("testBusy");
-      excludes.add("testFts5");
-      excludes.add("testSleep");
-      excludes.add("testToUtf8");
-      for(java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
+      for(final java.lang.reflect.Method m : Tester1.class.getDeclaredMethods()){
         final String name = m.getName();
-        if( name.startsWith("test") && excludes.indexOf(name)<0 ){
-          testMethods.add(m);
+        if( name.equals("testFail") ){
+          if( forceFail ){
+            testMethods.add(m);
+          }
+        }else if( !m.isAnnotationPresent( ManualTest.class ) ){
+          if( name.startsWith("test") ){
+            testMethods.add(m);
+          }
         }
       }
     }
@@ -1465,6 +1516,11 @@ public class Tester1 implements Runnable {
       }
     }
 
+    quietMode = squelchTestOutput;
+    outln("If you just saw warning messages regarding CallStaticObjectMethod, ",
+          "you are very likely seeing the side effects of a known openjdk8 ",
+          "bug. It is unsightly but does not affect the library.");
+
     final long timeStart = System.currentTimeMillis();
     int nLoop = 0;
     affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
@@ -1476,7 +1532,7 @@ public class Tester1 implements Runnable {
     outln("libversion_number: ",
           sqlite3_libversion_number(),"\n",
           sqlite3_libversion(),"\n",SQLITE_SOURCE_ID);
-    outln("Running ",nRepeat," loop(s) over ",nThread," thread(s).");
+    outln("Running ",nRepeat," loop(s) with ",nThread," thread(s) each.");
     if( takeNaps ) outln("Napping between tests is enabled.");
     for( int n = 0; n < nRepeat; ++n ){
       if( nThread==null || nThread<=1 ){
@@ -1500,6 +1556,7 @@ public class Tester1 implements Runnable {
         Thread.currentThread().interrupt();
       }
       if( !listErrors.isEmpty() ){
+        quietMode = false;
         outln("TEST ERRORS:");
         Exception err = null;
         for( Exception e : listErrors ){
@@ -1510,9 +1567,10 @@ public class Tester1 implements Runnable {
       }
     }
     outln();
+    quietMode = false;
 
     final long timeEnd = System.currentTimeMillis();
-    outln("Tests done. Metrics:");
+    outln("Tests done. Metrics across ",nTestRuns," total iteration(s):");
     outln("\tAssertions checked: ",affirmCount);
     outln("\tDatabases opened: ",metrics.dbOpen);
     if( doSomethingForDev ){
index 3856573169c0888c7917d6f322852319145d7fee..feb6d6303d3b25c27f03adbacdd54c46ec5dab76 100644 (file)
@@ -72,13 +72,18 @@ public class TesterFts5 {
     affirm( xDestroyCalled.value );
   }
 
-  public TesterFts5(){
-    final long timeStart = System.currentTimeMillis();
-    final int oldAffirmCount = Tester1.affirmCount;
-    test1();
-    final int affirmCount = Tester1.affirmCount - oldAffirmCount;
-    final long timeEnd = System.currentTimeMillis();
-    outln("FTS5 Tests done. Assertions checked = ",affirmCount,
-          ", Total time = ",(timeEnd - timeStart),"ms");
+  public TesterFts5(boolean verbose){
+    if(verbose){
+      final long timeStart = System.currentTimeMillis();
+      final int oldAffirmCount = Tester1.affirmCount;
+      test1();
+      final int affirmCount = Tester1.affirmCount - oldAffirmCount;
+      final long timeEnd = System.currentTimeMillis();
+      outln("FTS5 Tests done. Assertions checked = ",affirmCount,
+            ", Total time = ",(timeEnd - timeStart),"ms");
+    }else{
+      test1();
+    }
   }
+  public TesterFts5(){ this(false); }
 }
index f1c1761dd48a27dbe5a4396eb0f6b1aad47ad802..2da52989e34803c1c33da28b553b7bc175b2ebe0 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C JNI\scleanups\sregarding\sbuilding\swith\scertain\sfeatures\sdisabled.
-D 2023-08-23T17:52:51.175
+C Add\smore\sJNI\sdocs,\stests,\sand\sa\shandful\sof\sJava-side\soverloads.
+D 2023-08-24T11:57:51.863
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -232,13 +232,13 @@ 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 14b7c3abd1ae8693203b08b0e06bb359f8924ad2243f15953e9c6e456ae317b5
-F ext/jni/README.md 1693e865d366f5ebaa756732ea0d4b786515caf3cfbcd4dcb8758274373913b0
-F ext/jni/jar-dist.make 9a03d10dbb5a74c724bfec4b76fd9e4c9865cbbc858d731cb48f38ac897d73a3
-F ext/jni/src/c/sqlite3-jni.c 0ca96134d7fb3f313a7a49487f68a8d7a6d7545470c84532aa1ce63d2cdc432e
-F ext/jni/src/c/sqlite3-jni.h c5cb0348efe4e5f3d125a240e2437e8475de14a586c2f859e2acdcde4116244d
+F ext/jni/GNUmakefile 0a823c56f081294e7797dae303380ac989ebaa801bba970968342b7358f07aed
+F ext/jni/README.md 64bf1da0d562d051207ca1c5cfa52e8b7a69120533cc034a3da7670ef920cbef
+F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
+F ext/jni/src/c/sqlite3-jni.c e1e3cde4d08925282b5bc949f9ed8f613a6a2c6f60d0c697e79d59fb49f9fe4b
+F ext/jni/src/c/sqlite3-jni.h cc24d6742b29a52338ffd3b47caf923facb8ae77f9c2fc9c2de82673bf339ea2
 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892
-F ext/jni/src/org/sqlite/jni/AutoExtension.java 3b62c915e45ce73f63343ca9195ec63592244d616a1908b7587bdd45de1b97dd
+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/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8
@@ -257,9 +257,9 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7c
 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564
 F ext/jni/src/org/sqlite/jni/SQLFunction.java f697cf2a81c4119f2baf0682af689686f0466f1dd83dba00885f5603e693fe16
 F ext/jni/src/org/sqlite/jni/SQLLog.java c60610b35208416940822e834d61f08fbbe5d6e06b374b541b49e41fd56c9798
-F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f64554457fa30a048ef99374bfac3c4b986e3528353dce3086a98a858e3fe000
-F ext/jni/src/org/sqlite/jni/Tester1.java 69ea63a5b235f94f914dff6fe3ecd103ee0a8023b8737db071b46c0c75375e26
-F ext/jni/src/org/sqlite/jni/TesterFts5.java de095e3b701fba0c56d7b8b2993dc22bcbaa9de8f992904a93729ad729a91576
+F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 956063c854c4f662183c41c65d0ab48b5e2127824b8053eeb05b9fc40f0d09e3
+F ext/jni/src/org/sqlite/jni/Tester1.java b5a4bb2a969df053d5c138887f04039a79b36170372a2efdf5dfbd6ac90db4c9
+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/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee
@@ -2094,8 +2094,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 d67255f7251cc5d1d27d77d4c84ff216e2da71202db989718189a6b4beff1cd0
-R b911867dcaf18c3e131e156c82d306fe
+P a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18
+R cb79df5ad40cc86377d98f5a1329b589
 U stephan
-Z bdef86836c9254e732e1b6d744febf17
+Z 39e8fbdcbc2ebaa58ed84b96f0374c06
 # Remove this line to create a well-formed Fossil manifest.
index 5245bf9cfee9100c066cb1ebd7356caec2268422..180d5748464baf3301489867b675b015d8812d06 100644 (file)
@@ -1 +1 @@
-a9e6d5158b8a4a6b8554a5f8f0a35785ee450d42ea877275dc27085e89716c18
\ No newline at end of file
+d19a431facbde6a6b960664674753ee85d2c051a76109ce7db0b079c65fbdea0
\ No newline at end of file