** 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.*;
/**
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){
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();
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);
}
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);
+ }
}
/**
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));
xerrCode = sqlite3_extended_errcode(db);
errOffset = sqlite3_error_offset(db);
sysErrno = sqlite3_system_errno(db);
- db.close();
}
/**
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; }
}
}
+ 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 {
-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
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
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.
-793bbfa5af9721bc3a61e8e5eda46dfce2f5ff3f223a7564c9e1b09f11e53cb3
\ No newline at end of file
+a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249
\ No newline at end of file