From: stephan Date: Wed, 11 Oct 2023 13:52:05 +0000 (+0000) Subject: Add (prepare, step, reset, finalize) parts of the JNI level-2 stmt wrapper and associ... X-Git-Tag: version-3.44.0~131 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=582d65cce31cc4e17124ceac92fcba89ff0e52d0;p=thirdparty%2Fsqlite.git Add (prepare, step, reset, finalize) parts of the JNI level-2 stmt wrapper and associated tests. FossilOrigin-Name: a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249 --- diff --git a/ext/jni/src/org/sqlite/jni/Sqlite.java b/ext/jni/src/org/sqlite/jni/Sqlite.java index b964c57b33..5c2c456296 100644 --- a/ext/jni/src/org/sqlite/jni/Sqlite.java +++ b/ext/jni/src/org/sqlite/jni/Sqlite.java @@ -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); + } } diff --git a/ext/jni/src/org/sqlite/jni/SqliteException.java b/ext/jni/src/org/sqlite/jni/SqliteException.java index 3da5d8c583..c15cb34919 100644 --- a/ext/jni/src/org/sqlite/jni/SqliteException.java +++ b/ext/jni/src/org/sqlite/jni/SqliteException.java @@ -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; } diff --git a/ext/jni/src/org/sqlite/jni/Tester2.java b/ext/jni/src/org/sqlite/jni/Tester2.java index b7701f1a92..e75f56e064 100644 --- a/ext/jni/src/org/sqlite/jni/Tester2.java +++ b/ext/jni/src/org/sqlite/jni/Tester2.java @@ -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 { diff --git a/manifest b/manifest index 9f914a5a70..560c66a995 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index 7f818d81f4..7b40a7c4e7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -793bbfa5af9721bc3a61e8e5eda46dfce2f5ff3f223a7564c9e1b09f11e53cb3 \ No newline at end of file +a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249 \ No newline at end of file