]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add (prepare, step, reset, finalize) parts of the JNI level-2 stmt wrapper and associ...
authorstephan <stephan@noemail.net>
Wed, 11 Oct 2023 13:52:05 +0000 (13:52 +0000)
committerstephan <stephan@noemail.net>
Wed, 11 Oct 2023 13:52:05 +0000 (13:52 +0000)
FossilOrigin-Name: a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249

ext/jni/src/org/sqlite/jni/Sqlite.java
ext/jni/src/org/sqlite/jni/SqliteException.java
ext/jni/src/org/sqlite/jni/Tester2.java
manifest
manifest.uuid

index b964c57b33ca6163d68e81c7feedc161b94676f5..5c2c456296872abf62be5b2f6db5d0875f30cfc9 100644 (file)
@@ -12,6 +12,7 @@
 ** This file is part of the JNI bindings for the sqlite3 C API.
 */
 package org.sqlite.jni;
+import java.nio.charset.StandardCharsets;
 import static org.sqlite.jni.CApi.*;
 
 /**
@@ -22,7 +23,7 @@ import static org.sqlite.jni.CApi.*;
    individual instances are tied to a specific database connection.
 */
 public final class Sqlite implements AutoCloseable  {
-  private sqlite3 db = null;
+  private sqlite3 db;
 
   //! Used only by the open() factory functions.
   private Sqlite(sqlite3 db){
@@ -33,6 +34,9 @@ public final class Sqlite implements AutoCloseable  {
      Returns a newly-opened db connection or throws SqliteException if
      opening fails. All arguments are as documented for
      sqlite3_open_v2().
+
+     Design question: do we want static factory functions or should
+     this be reformulated as a constructor?
   */
   public static Sqlite open(String filename, int flags, String vfsName){
     final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
@@ -40,7 +44,9 @@ public final class Sqlite implements AutoCloseable  {
     final sqlite3 n = out.take();
     if( 0!=rc ){
       if( null==n ) throw new SqliteException(rc);
-      else throw new SqliteException(n);
+      final SqliteException ex = new SqliteException(n);
+      n.close();
+      throw ex;
     }
     return new Sqlite(n);
   }
@@ -64,6 +70,120 @@ public final class Sqlite implements AutoCloseable  {
      Returns this object's underlying native db handle, or null if
      this instance has been closed.
   */
-  sqlite3 dbHandle(){ return this.db; }
+  sqlite3 nativeHandle(){ return this.db; }
+
+  private void affirmOpen(){
+    if( null==db || 0==db.getNativePointer() ){
+      throw new IllegalArgumentException("This database instance is closed.");
+    }
+  }
+
+  // private byte[] stringToUtf8(String s){
+  //   return s==null ? null : s.getBytes(StandardCharsets.UTF_8);
+  // }
+
+  private void affirmRcOk(int rc){
+    if( 0!=rc ){
+      throw new SqliteException(db);
+    }
+  }
+
+  public final class Stmt implements AutoCloseable {
+    private Sqlite _db = null;
+    private sqlite3_stmt stmt = null;
+    /** Only called by the prepare() factory functions. */
+    Stmt(Sqlite db, sqlite3_stmt stmt){
+      this._db = db;
+      this.stmt = stmt;
+    }
+
+    sqlite3_stmt nativeHandle(){
+      return stmt;
+    }
+
+    private sqlite3_stmt affirmOpen(){
+      if( null==stmt || 0==stmt.getNativePointer() ){
+        throw new IllegalArgumentException("This Stmt has been finalized.");
+      }
+      return stmt;
+    }
+
+    /**
+       Corresponds to sqlite3_finalize(), but we cannot override the
+       name finalize() here because this one requires a different
+       signature. We do not throw on error here because "destructors
+       do not throw." If it returns non-0, the object is still
+       finalized.
+    */
+    public int finalizeStmt(){
+      int rc = 0;
+      if( null!=stmt ){
+        sqlite3_finalize(stmt);
+        stmt = null;
+      }
+      return rc;
+    }
+
+    @Override public void close(){
+      finalizeStmt();
+    }
+
+    /**
+       Throws if rc is any value other than 0, SQLITE_ROW, or
+       SQLITE_DONE, else returns rc.
+    */
+    private int checkRc(int rc){
+      switch(rc){
+        case 0:
+        case SQLITE_ROW:
+        case SQLITE_DONE: return rc;
+        default:
+          throw new SqliteException(this);
+      }
+    }
+
+    /**
+       Works like sqlite3_step() but throws SqliteException for any
+       result other than 0, SQLITE_ROW, or SQLITE_DONE.
+    */
+    public int step(){
+      return checkRc(sqlite3_step(affirmOpen()));
+    }
+
+    public Sqlite db(){ return this._db; }
+
+    /**
+       Works like sqlite3_reset() but throws on error.
+    */
+    public void reset(){
+      checkRc(sqlite3_reset(affirmOpen()));
+    }
+
+    public void clearBindings(){
+      sqlite3_clear_bindings( affirmOpen() );
+    }
+  }
+
+
+  /**
+     prepare() TODOs include:
+
+     - overloads taking byte[] and ByteBuffer.
+
+     - multi-statement processing, like CApi.sqlite3_prepare_multi()
+     but using a callback specific to the higher-level Stmt class
+     rather than the sqlite3_stmt class.
+  */
+  public Stmt prepare(String sql, int prepFlags){
+    affirmOpen();
+    final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
+    final int rc = sqlite3_prepare_v3(this.db, sql, prepFlags, out);
+    affirmRcOk(rc);
+    return new Stmt(this, out.take());
+  }
+
+  public Stmt prepare(String sql){
+    return prepare(sql, 0);
+  }
 
 }
index 3da5d8c583c2b9ef7aae6a0be213672bca3f72e9..c15cb3491938f0896e7fc91331c2617ad9109ba9 100644 (file)
@@ -46,7 +46,12 @@ public final class SqliteException extends java.lang.RuntimeException {
 
   /**
      Records the current error state of db (which must not be null and
-     must refer to an opened db object) then closes it.
+     must refer to an opened db object). Note that this does NOT close
+     the db.
+
+     Design note: closing the db on error is likely only useful during
+     a failed db-open operation, and the place(s) where that can
+     happen are inside this library, not client-level code.
   */
   public SqliteException(sqlite3 db){
     super(sqlite3_errmsg(db));
@@ -54,7 +59,6 @@ public final class SqliteException extends java.lang.RuntimeException {
     xerrCode = sqlite3_extended_errcode(db);
     errOffset = sqlite3_error_offset(db);
     sysErrno = sqlite3_system_errno(db);
-    db.close();
   }
 
   /**
@@ -62,7 +66,11 @@ public final class SqliteException extends java.lang.RuntimeException {
      refer to an open database) then closes it.
   */
   public SqliteException(Sqlite db){
-    this(db.dbHandle());
+    this(db.nativeHandle());
+  }
+
+  public SqliteException(Sqlite.Stmt stmt){
+    this( stmt.db() );
   }
 
   public int errcode(){ return errCode; }
index b7701f1a928fd4fbd007ddb671b0777a8fd8cc4d..e75f56e064179304bb6ba5fc8e4d26ca032241c5 100644 (file)
@@ -126,16 +126,51 @@ public class Tester2 implements Runnable {
     }
   }
 
+  Sqlite openDb(String name){
+    return Sqlite.open(name, SQLITE_OPEN_READWRITE|
+                       SQLITE_OPEN_CREATE|
+                       SQLITE_OPEN_EXRESCODE);
+  }
+
+  Sqlite openDb(){ return openDb(":memory:"); }
+
   void testOpenDb1(){
-    Sqlite db = Sqlite.open(":memory:");
-    affirm( 0!=db.dbHandle().getNativePointer() );
+    Sqlite db = openDb();
+    affirm( 0!=db.nativeHandle().getNativePointer() );
     db.close();
-    affirm( null==db.dbHandle() );
+    affirm( null==db.nativeHandle() );
+
+    SqliteException ex = null;
+    try {
+      db = openDb("/no/such/dir/.../probably");
+    }catch(SqliteException e){
+      ex = e;
+    }
+    affirm( ex!=null );
+    affirm( ex.errcode() != 0 );
+    affirm( ex.extendedErrcode() != 0 );
+    affirm( ex.errorOffset() < 0 );
+    // there's no reliable way to predict what ex.systemErrno() might be
   }
 
-  @ManualTest /* because we only want to run this test on demand */
-  private void testFail(){
-    affirm( false, "Intentional failure." );
+  void testPrepare1(){
+    try (Sqlite db = openDb()) {
+      Sqlite.Stmt stmt = db.prepare("SELECT 1");
+      affirm( null!=stmt.nativeHandle() );
+      affirm( SQLITE_ROW == stmt.step() );
+      affirm( SQLITE_DONE == stmt.step() );
+      stmt.reset();
+      affirm( SQLITE_ROW == stmt.step() );
+      affirm( SQLITE_DONE == stmt.step() );
+      affirm( 0 == stmt.finalizeStmt() );
+      affirm( null==stmt.nativeHandle() );
+
+      stmt = db.prepare("SELECT 1");
+      affirm( SQLITE_ROW == stmt.step() );
+      affirm( 0 == stmt.finalizeStmt() )
+        /* getting a non-0 out of sqlite3_finalize() is tricky */;
+      affirm( null==stmt.nativeHandle() );
+    }
   }
 
   private void runTests(boolean fromThread) throws Exception {
index 9f914a5a70ab5c7730e45c08a088025888db509e..560c66a99545e56e88202666b335e2ca274840b8 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Simplification\sto\ssqlite3ApiExit().\s\sGenerates\sidentical\smachine\scode,\sbut\neasier\sfor\shumans\sto\sread.
-D 2023-10-11T13:34:18.514
+C Add\s(prepare,\sstep,\sreset,\sfinalize)\sparts\sof\sthe\sJNI\slevel-2\sstmt\swrapper\sand\sassociated\stests.
+D 2023-10-11T13:52:05.849
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -262,11 +262,11 @@ F ext/jni/src/org/sqlite/jni/RollbackHookCallback.java ec6cd96bff5d3bc5af079cbf1
 F ext/jni/src/org/sqlite/jni/SQLFunction.java 544a875d33fd160467d82e2397ac33157b29971d715a821a4fad3c899113ee8c
 F ext/jni/src/org/sqlite/jni/SQLTester.java d246c67f93e2fa2603bd106dbb3246ea725c987dffd6e5d42214ae262f750c68
 F ext/jni/src/org/sqlite/jni/ScalarFunction.java 6d387bb499fbe3bc13c53315335233dbf6a0c711e8fa7c521683219b041c614c
-F ext/jni/src/org/sqlite/jni/Sqlite.java 713f973764de9f918500b8723f347e67d29da226ad34b18e1f37865397c0efcb
-F ext/jni/src/org/sqlite/jni/SqliteException.java f5d17a10202c0983fb074f66a0b48cf1e573b1da2eaeda679825e3edc1829706
+F ext/jni/src/org/sqlite/jni/Sqlite.java 1617ea2bf3dfa493b7f031a3187cbfd6837c39bc1d406c4b3edcf9aab941639d
+F ext/jni/src/org/sqlite/jni/SqliteException.java e17500e8bca2c68c260d8d0163fe4b7dc8bd0b1b90211201325c4a5566ce75ca
 F ext/jni/src/org/sqlite/jni/TableColumnMetadata.java 54511b4297fa28dcb3f49b24035e34ced10e3fd44fd0e458e784f4d6b0096dab
 F ext/jni/src/org/sqlite/jni/Tester1.java f7b85fe24cf6c3e43bdf7e390617657e8137359f804d76921829c2a8c41b6df1
-F ext/jni/src/org/sqlite/jni/Tester2.java 3e7b3c05c08bdbf899684074f095724e1853dc16912dfb53306a03e5c4cbd614
+F ext/jni/src/org/sqlite/jni/Tester2.java 70e005d41060e398ec0f69bd39a8e1c376fd51f81629cf25e877889ec9cb6ec6
 F ext/jni/src/org/sqlite/jni/TesterFts5.java d60fe9944a81156b3b5325dd1b0e8e92a1547468f39fd1266d06f7bb6a95fa70
 F ext/jni/src/org/sqlite/jni/TraceV2Callback.java f157edd9c72e7d2243c169061487cd7bb51a0d50f3ac976dbcbbacf748ab1fc2
 F ext/jni/src/org/sqlite/jni/UpdateHookCallback.java 959d4677a857c9079c6e96ddd10918b946d68359af6252b6f284379069ea3d27
@@ -2128,8 +2128,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 65ccf5fef812d43aed9e00af36c90e1a499d197e30148753790445e25ee1324c
-R af91c5cebf078db7ff8defd10c43084e
-U drh
-Z ee511a7822e958c17cba502a0e5ae3ed
+P 793bbfa5af9721bc3a61e8e5eda46dfce2f5ff3f223a7564c9e1b09f11e53cb3
+R 4d3cf5230b57585fb263b4c0d4fb2b1e
+U stephan
+Z 39c6ded9773037a11fc3746bbfb10c12
 # Remove this line to create a well-formed Fossil manifest.
index 7f818d81f4056d9e63f837744f0ea7f150aa28bf..7b40a7c4e7781697ce575fd5ff8637157e942009 100644 (file)
@@ -1 +1 @@
-793bbfa5af9721bc3a61e8e5eda46dfce2f5ff3f223a7564c9e1b09f11e53cb3
\ No newline at end of file
+a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249
\ No newline at end of file