diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/jni/src/c/sqlite3-jni.c | 8 | ||||
-rw-r--r-- | ext/jni/src/org/sqlite/jni/Sqlite.java | 126 | ||||
-rw-r--r-- | ext/jni/src/org/sqlite/jni/SqliteException.java | 14 | ||||
-rw-r--r-- | ext/jni/src/org/sqlite/jni/Tester2.java | 47 | ||||
-rw-r--r-- | ext/wasm/api/sqlite3-wasm.c | 8 |
5 files changed, 191 insertions, 12 deletions
diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index ab8dac644..7afc9a0f0 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -43,6 +43,14 @@ /**********************************************************************/ /* SQLITE_ENABLE_... */ +/* +** Unconditionally enable API_ARMOR in the JNI build. It ensures that +** public APIs behave predictable in the face of passing illegal NULLs +** or ranges which might otherwise invoke undefined behavior. +*/ +#undef SQLITE_ENABLE_API_ARMOR +#define SQLITE_ENABLE_API_ARMOR 1 + #ifndef SQLITE_ENABLE_BYTECODE_VTAB # define SQLITE_ENABLE_BYTECODE_VTAB 1 #endif diff --git a/ext/jni/src/org/sqlite/jni/Sqlite.java b/ext/jni/src/org/sqlite/jni/Sqlite.java index b964c57b3..5c2c45629 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 3da5d8c58..c15cb3491 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 b7701f1a9..e75f56e06 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/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index db77010d9..88a679c51 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -84,6 +84,14 @@ /**********************************************************************/ /* SQLITE_ENABLE_... */ +/* +** Unconditionally enable API_ARMOR in the WASM build. It ensures that +** public APIs behave predictable in the face of passing illegal NULLs +** or ranges which might otherwise invoke undefined behavior. +*/ +#undef SQLITE_ENABLE_API_ARMOR +#define SQLITE_ENABLE_API_ARMOR 1 + #ifndef SQLITE_ENABLE_BYTECODE_VTAB # define SQLITE_ENABLE_BYTECODE_VTAB 1 #endif |