mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Add (prepare, step, reset, finalize) parts of the JNI level-2 stmt wrapper and associated tests.
FossilOrigin-Name: a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -126,16 +126,51 @@ public class Tester2 implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
void testOpenDb1(){
|
||||
Sqlite db = Sqlite.open(":memory:");
|
||||
affirm( 0!=db.dbHandle().getNativePointer() );
|
||||
db.close();
|
||||
affirm( null==db.dbHandle() );
|
||||
Sqlite openDb(String name){
|
||||
return Sqlite.open(name, SQLITE_OPEN_READWRITE|
|
||||
SQLITE_OPEN_CREATE|
|
||||
SQLITE_OPEN_EXRESCODE);
|
||||
}
|
||||
|
||||
@ManualTest /* because we only want to run this test on demand */
|
||||
private void testFail(){
|
||||
affirm( false, "Intentional failure." );
|
||||
Sqlite openDb(){ return openDb(":memory:"); }
|
||||
|
||||
void testOpenDb1(){
|
||||
Sqlite db = openDb();
|
||||
affirm( 0!=db.nativeHandle().getNativePointer() );
|
||||
db.close();
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user