aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/jni/src/c/sqlite3-jni.c8
-rw-r--r--ext/jni/src/org/sqlite/jni/Sqlite.java126
-rw-r--r--ext/jni/src/org/sqlite/jni/SqliteException.java14
-rw-r--r--ext/jni/src/org/sqlite/jni/Tester2.java47
-rw-r--r--ext/wasm/api/sqlite3-wasm.c8
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