]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Reimplement auto-extensions in Java for use with the JNI wrapper1 API.
authorstephan <stephan@noemail.net>
Sat, 4 Nov 2023 21:51:34 +0000 (21:51 +0000)
committerstephan <stephan@noemail.net>
Sat, 4 Nov 2023 21:51:34 +0000 (21:51 +0000)
FossilOrigin-Name: 14ed4c64533622e5faf1aaa59c24885885aad43f1c0d4717773e79440e8e1468

ext/jni/src/org/sqlite/jni/capi/CApi.java
ext/jni/src/org/sqlite/jni/capi/Tester1.java
ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java
ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java
ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java
manifest
manifest.uuid

index ef1ca17b2217d28c2d9e5012f879d1a76b3b9e98..79568e74adb96d00432594a292e207654b092e4d 100644 (file)
@@ -891,7 +891,7 @@ public final class CApi {
   }
 
   public static native boolean sqlite3_extended_result_codes(
-    @NotNull sqlite3 db, boolean onoff
+    @NotNull sqlite3 db, boolean on
   );
 
   static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
index fdadb3e29d9a4a43bc66c854ddfd16791d937780..479b5f72bce09b9dbffcb1bec56a2779b9aa5128 100644 (file)
@@ -1419,7 +1419,7 @@ public class Tester1 implements Runnable {
 
     val.value = 0;
     final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
-        @Override public synchronized int call(sqlite3 db){
+        @Override public int call(sqlite3 db){
           ++val.value;
           return 0;
         }
index 941800513d61ec24b726f855e4f6e5021c3a38f5..b3317029c7f3c38bb7f407acca6f832d798c4a50 100644 (file)
@@ -117,6 +117,10 @@ public interface SqlFunction  {
     public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
     public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
     public void resultNull(){CApi.sqlite3_result_null(cx);}
+    /**
+       Analog to sqlite3_result_value(), using the Value object at the
+       given argument index.
+    */
     public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
     public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);}
     public void resultZeroBlob(long n){
index 4f14253a3e7f3a1a8a75236135e7ac0954288048..298b6ae55e81d70d3a1603469a203a70c49fb898 100644 (file)
@@ -13,7 +13,6 @@
 */
 package org.sqlite.jni.wrapper1;
 import java.nio.charset.StandardCharsets;
-import static org.sqlite.jni.capi.CApi.*;
 import org.sqlite.jni.capi.CApi;
 import org.sqlite.jni.capi.sqlite3;
 import org.sqlite.jni.capi.sqlite3_stmt;
@@ -129,7 +128,7 @@ public final class Sqlite implements AutoCloseable  {
   */
   public static Sqlite open(String filename, int flags, String vfsName){
     final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
-    final int rc = sqlite3_open_v2(filename, out, flags, vfsName);
+    final int rc = CApi.sqlite3_open_v2(filename, out, flags, vfsName);
     final sqlite3 n = out.take();
     if( 0!=rc ){
       if( null==n ) throw new SqliteException(rc);
@@ -137,10 +136,11 @@ public final class Sqlite implements AutoCloseable  {
       n.close();
       throw ex;
     }
-    Sqlite rv = new Sqlite(n);
+    final Sqlite rv = new Sqlite(n);
     synchronized(nativeToWrapper){
       nativeToWrapper.put(n, rv);
     }
+    runAutoExtensions(rv);
     return rv;
   }
 
@@ -149,7 +149,7 @@ public final class Sqlite implements AutoCloseable  {
   }
 
   public static Sqlite open(String filename){
-    return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
+    return open(filename, OPEN_READWRITE|OPEN_CREATE, null);
   }
 
   public static String libVersion(){
@@ -308,7 +308,7 @@ public final class Sqlite implements AutoCloseable  {
     if( 0!=rc ){
       if( CApi.SQLITE_NOMEM==rc ){
         throw new OutOfMemoryError();
-      }else if( null==db || 0==sqlite3_errcode(db)){
+      }else if( null==db || 0==CApi.sqlite3_errcode(db)){
         throw new SqliteException(rc);
       }else{
         throw new SqliteException(db);
@@ -343,7 +343,7 @@ public final class Sqlite implements AutoCloseable  {
   */
   public Stmt prepare(String sql, int prepFlags){
     final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
-    final int rc = sqlite3_prepare_v3(thisDb(), sql, prepFlags, out);
+    final int rc = CApi.sqlite3_prepare_v3(thisDb(), sql, prepFlags, out);
     checkRc(rc);
     final sqlite3_stmt q = out.take();
     if( null==q ){
@@ -724,7 +724,7 @@ public final class Sqlite implements AutoCloseable  {
         synchronized(nativeToWrapper){
           nativeToWrapper.remove(this.stmt);
         }
-        sqlite3_finalize(stmt);
+        CApi.sqlite3_finalize(stmt);
         stmt = null;
         _db = null;
         resultColCount = 0;
@@ -745,8 +745,8 @@ public final class Sqlite implements AutoCloseable  {
     private int checkRc(int rc){
       switch(rc){
         case 0:
-        case SQLITE_ROW:
-        case SQLITE_DONE: return rc;
+        case CApi.SQLITE_ROW:
+        case CApi.SQLITE_DONE: return rc;
         default:
           if( null==stmt ) throw new SqliteException(rc);
           else throw new SqliteException(this);
@@ -759,7 +759,7 @@ public final class Sqlite implements AutoCloseable  {
        result.
     */
     public boolean step(){
-      switch(checkRc(sqlite3_step(thisStmt()))){
+      switch(checkRc(CApi.sqlite3_step(thisStmt()))){
         case CApi.SQLITE_ROW: return true;
         case CApi.SQLITE_DONE: return false;
         default:
@@ -930,4 +930,93 @@ public final class Sqlite implements AutoCloseable  {
     }
   } /* Stmt class */
 
+  /**
+     Interface for auto-extensions, as per the
+     sqlite3_auto_extension() API.
+
+     Design note: the chicken/egg timing of auto-extension execution
+     requires that this feature be entirely re-implemented in Java
+     because the C-level API has no access to the Sqlite type so
+     cannot pass on an object of that type while the database is being
+     opened.  One side effect of this reimplementation is that this
+     class's list of auto-extensions is 100% independent of the
+     C-level list so, e.g., clearAutoExtensions() will have no effect
+     on auto-extensions added via the C-level API and databases opened
+     from that level of API will not be passed to this level's
+     AutoExtension instances.
+  */
+  public interface AutoExtension {
+    public void call(Sqlite db);
+  }
+
+  private static final java.util.Set<AutoExtension> autoExtensions =
+    new java.util.LinkedHashSet<>();
+
+  /**
+     Passes db to all auto-extensions. If any one of them throws,
+     db.close() is called before the exception is propagated.
+  */
+  private static void runAutoExtensions(Sqlite db){
+    AutoExtension list[];
+    synchronized(autoExtensions){
+      /* Avoid that modifications to the AutoExtension list from within
+         auto-extensions affect this execution of this list. */
+      list = autoExtensions.toArray(new AutoExtension[0]);
+    }
+    try {
+      for( AutoExtension ax : list ) ax.call(db);
+    }catch(Exception e){
+      db.close();
+      throw e;
+    }
+  }
+
+  /**
+     Analog to sqlite3_auto_extension(), adds the given object to the
+     list of auto-extensions if it is not already in that list. The
+     given object will be run as part of Sqlite.open(), and passed the
+     being-opened database. If the extension throws then open() will
+     fail.
+
+     This API does not guaranty whether or not manipulations made to
+     the auto-extension list from within auto-extension callbacks will
+     affect the current traversal of the auto-extension list.  Whether
+     or not they do is unspecified and subject to change between
+     versions. e.g. if an AutoExtension calls addAutoExtension(),
+     whether or not the new extension will be run on the being-opened
+     database is undefined.
+
+     Note that calling Sqlite.open() from an auto-extension will
+     necessarily result in recursion loop and (eventually) a stack
+     overflow.
+  */
+  public static void addAutoExtension( AutoExtension e ){
+    if( null==e ){
+      throw new IllegalArgumentException("AutoExtension may not be null.");
+    }
+    synchronized(autoExtensions){
+      autoExtensions.add(e);
+    }
+  }
+
+  /**
+     Removes the given object from the auto-extension list if it is in
+     that list, otherwise this has no side-effects beyond briefly
+     locking that list.
+  */
+  public static void removeAutoExtension( AutoExtension e ){
+    synchronized(autoExtensions){
+      autoExtensions.remove(e);
+    }
+  }
+
+  /**
+     Removes all auto-extensions which were added via addAutoExtension().
+  */
+  public static void clearAutoExtensions(){
+    synchronized(autoExtensions){
+      autoExtensions.clear();
+    }
+  }
+
 }
index 5f991da4d3832f7a782a62753d76fb6dfae70d9e..4402ea6b3a7ac4981b6f20186e9f4c8a4a61f172 100644 (file)
@@ -526,6 +526,83 @@ public class Tester2 implements Runnable {
     db.close();
   }
 
+  @SingleThreadOnly /* 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);
+    final Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){
+        @Override public void call(Sqlite db){
+          ++val.value;
+          if( null!=toss.value ){
+            throw new RuntimeException(toss.value);
+          }
+        }
+      };
+    Sqlite.addAutoExtension(ax);
+    openDb().close();
+    affirm( 1==val.value );
+    openDb().close();
+    affirm( 2==val.value );
+    Sqlite.clearAutoExtensions();
+    openDb().close();
+    affirm( 2==val.value );
+
+    Sqlite.addAutoExtension( ax );
+    Sqlite.addAutoExtension( ax ); // Must not add a second entry
+    Sqlite.addAutoExtension( ax ); // or a third one
+    openDb().close();
+    affirm( 3==val.value );
+
+    Sqlite db = openDb();
+    affirm( 4==val.value );
+    execSql(db, "ATTACH ':memory:' as foo");
+    affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
+    db.close();
+    db = null;
+
+    Sqlite.removeAutoExtension(ax);
+    openDb().close();
+    affirm( 4==val.value );
+    Sqlite.addAutoExtension(ax);
+    Exception err = null;
+    toss.value = "Throwing from auto_extension.";
+    try{
+      openDb();
+    }catch(Exception e){
+      err = e;
+    }
+    affirm( err!=null );
+    affirm( err.getMessage().indexOf(toss.value)>=0 );
+    toss.value = null;
+
+    val.value = 0;
+    final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){
+        @Override public void call(Sqlite db){
+          ++val.value;
+        }
+      };
+    Sqlite.addAutoExtension(ax2);
+    openDb().close();
+    affirm( 2 == val.value );
+    Sqlite.removeAutoExtension(ax);
+    openDb().close();
+    affirm( 3 == val.value );
+    Sqlite.addAutoExtension(ax);
+    openDb().close();
+    affirm( 5 == val.value );
+    Sqlite.removeAutoExtension(ax2);
+    openDb().close();
+    affirm( 6 == val.value );
+    Sqlite.addAutoExtension(ax2);
+    openDb().close();
+    affirm( 8 == val.value );
+
+    Sqlite.clearAutoExtensions();
+    openDb().close();
+    affirm( 8 == val.value );
+  }
+
   private void runTests(boolean fromThread) throws Exception {
     List<java.lang.reflect.Method> mlist = testMethods;
     affirm( null!=mlist );
index eacc52b68c1a1c432ce35ec95c773ef8e28864a6..23488e42adac222a2904b6fc8cd6231d9f22de61 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Ensure\sthat\sthe\sYYYY-MM-DD\sinput\sto\sdate\sand\stime\sfunctions\shas\sbeen\snormalized\nprior\sto\sreturning\sa\sresult.\n[forum:/forumpost/6bb476897e|Forum\spost\s6bb476897e].
-D 2023-11-04T21:44:00.659
+C Reimplement\sauto-extensions\sin\sJava\sfor\suse\swith\sthe\sJNI\swrapper1\sAPI.
+D 2023-11-04T21:51:34.589
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63
 F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013
 F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759
 F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca
-F ext/jni/src/org/sqlite/jni/capi/CApi.java d21e6c1c4557ae18bbc2eefb0882efdb36fdaecdc58823c142def994327a365b
+F ext/jni/src/org/sqlite/jni/capi/CApi.java 4043d709626079cce6d524ef49122b934c043022bd88bc1e72eb697ac8df86e7
 F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b
 F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
 F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95
@@ -269,7 +269,7 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385
 F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1
 F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615
 F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f
-F ext/jni/src/org/sqlite/jni/capi/Tester1.java 41e2b910a11dfdd4cc39ab608492d7c12f3791e85ac7f9d75d5445f7645a5e57
+F ext/jni/src/org/sqlite/jni/capi/Tester1.java 96c27ae10ec44ce5f6a150e8bc6525d86ab2d9118da18649943a0bf4d8d206ce
 F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
 F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4
 F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950
@@ -295,10 +295,10 @@ F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ad
 F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
 F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483
 F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
-F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java e787f5f36d5832fe3c7a000a8609eb0629fb160b95f8f25566df13e72e6f5470
-F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 3e813aa4a680948a1885a5df1537c9245b3b7362aaf6aa31f679640e81da020e
+F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2833afdb9af5c1949bb35f4c926a5351fba9d1cdf0996864caa7b47827a346c7
+F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 3da22cb18d8544fff1c7184aeaa2516c20d63e8a31db848eb7470ce285b284dc
 F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 929a1e2ab4e135fbbae7f0d2d609f77cfbbc60bbec7ba789ce23d9c73bc6156e
-F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 96d7908da8bad591aff8f192cb83e038fd5861ef4601726eeda24905422718c9
+F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 03638a1774a95bcc7b5de440a5f1398720460e30fc480032a2e8be24e997d30c
 F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
 F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f
 F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
@@ -2142,8 +2142,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 c23123af7d40dea24a0848dff987fd58a6703ce04165060533544db85983d566
-R 0a52dbd298d991529b412673e9dc7eb5
-U drh
-Z 33b6056e932d878d8676b163d8b94db2
+P b692eb8ccb2d0645599ad73a8bdacf5df499114244aadeb38aabc580fc4dc7c5
+R 99817a8518af3f567a07823f0172a1fb
+U stephan
+Z 276f4a4bca9307a54b6144f3c8e4f323
 # Remove this line to create a well-formed Fossil manifest.
index a2ff9c7a2aedbd02717e4b304a4ddb7b44aaee28..89c3bcfb2b92f6a6f2f312efeac25e64270256ad 100644 (file)
@@ -1 +1 @@
-b692eb8ccb2d0645599ad73a8bdacf5df499114244aadeb38aabc580fc4dc7c5
\ No newline at end of file
+14ed4c64533622e5faf1aaa59c24885885aad43f1c0d4717773e79440e8e1468
\ No newline at end of file