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 {
|
||||
|
18
manifest
18
manifest
@ -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.
|
||||
|
@ -1 +1 @@
|
||||
793bbfa5af9721bc3a61e8e5eda46dfce2f5ff3f223a7564c9e1b09f11e53cb3
|
||||
a7082f186f2b9b6666bbc65f2eadeb74d91fa0a681e3b2468b261ffd322bd249
|
Reference in New Issue
Block a user