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 {

View File

@ -1,5 +1,5 @@
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
@ -262,11 +262,11 @@ F ext/jni/src/org/sqlite/jni/RollbackHookCallback.java ec6cd96bff5d3bc5af079cbf1
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
@ -2128,8 +2128,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
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.

View File

@ -1 +1 @@
793bbfa5af9721bc3a61e8e5eda46dfce2f5ff3f223a7564c9e1b09f11e53cb3
a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249