1
0
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:
stephan
2023-10-11 13:52:05 +00:00
parent 58c7b770de
commit 582d65cce3
5 changed files with 187 additions and 24 deletions

View File

@ -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);
}
}

View File

@ -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; }

View File

@ -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 {