mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Add busy-handler support to JNI wrapper1.
FossilOrigin-Name: dcf579ab2de4a3d3a437cde59b2fd60f1729c0bde31df1865117e6a5ea4bab20
This commit is contained in:
@ -1031,49 +1031,48 @@ public class Tester1 implements Runnable {
|
||||
@SingleThreadOnly /* because threads inherently break this test */
|
||||
private static void testBusy(){
|
||||
final String dbName = "_busy-handler.db";
|
||||
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
|
||||
int rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
final sqlite3 db1 = outDb.get();
|
||||
execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
|
||||
rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
affirm( outDb.get() != db1 );
|
||||
final sqlite3 db2 = outDb.get();
|
||||
|
||||
affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
|
||||
rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
|
||||
affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
|
||||
affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
|
||||
affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) );
|
||||
|
||||
final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
|
||||
BusyHandlerCallback handler = new BusyHandlerCallback(){
|
||||
@Override public int call(int n){
|
||||
//outln("busy handler #"+n);
|
||||
return n > 2 ? 0 : ++xBusyCalled.value;
|
||||
}
|
||||
};
|
||||
rc = sqlite3_busy_handler(db2, handler);
|
||||
affirm(0 == rc);
|
||||
|
||||
// Force a locked condition...
|
||||
execSql(db1, "BEGIN EXCLUSIVE");
|
||||
rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
|
||||
affirm( SQLITE_BUSY == rc);
|
||||
affirm( null == outStmt.get() );
|
||||
affirm( 3 == xBusyCalled.value );
|
||||
sqlite3_close_v2(db1);
|
||||
sqlite3_close_v2(db2);
|
||||
try{
|
||||
final java.io.File f = new java.io.File(dbName);
|
||||
f.delete();
|
||||
}catch(Exception e){
|
||||
/* ignore */
|
||||
final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3();
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
|
||||
int rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
final sqlite3 db1 = outDb.get();
|
||||
execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
|
||||
rc = sqlite3_open(dbName, outDb);
|
||||
++metrics.dbOpen;
|
||||
affirm( 0 == rc );
|
||||
affirm( outDb.get() != db1 );
|
||||
final sqlite3 db2 = outDb.get();
|
||||
|
||||
affirm( "main".equals( sqlite3_db_name(db1, 0) ) );
|
||||
rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo");
|
||||
affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) );
|
||||
affirm( "foo".equals( sqlite3_db_name(db1, 0) ) );
|
||||
affirm( SQLITE_MISUSE == sqlite3_db_config(db1, 0, 0, null) );
|
||||
|
||||
final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
|
||||
BusyHandlerCallback handler = new BusyHandlerCallback(){
|
||||
@Override public int call(int n){
|
||||
//outln("busy handler #"+n);
|
||||
return n > 2 ? 0 : ++xBusyCalled.value;
|
||||
}
|
||||
};
|
||||
rc = sqlite3_busy_handler(db2, handler);
|
||||
affirm(0 == rc);
|
||||
|
||||
// Force a locked condition...
|
||||
execSql(db1, "BEGIN EXCLUSIVE");
|
||||
rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt);
|
||||
affirm( SQLITE_BUSY == rc);
|
||||
affirm( null == outStmt.get() );
|
||||
affirm( 3 == xBusyCalled.value );
|
||||
sqlite3_close_v2(db1);
|
||||
sqlite3_close_v2(db2);
|
||||
}finally{
|
||||
try{(new java.io.File(dbName)).delete();}
|
||||
catch(Exception e){/* ignore */}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
public static final int OPEN_READWRITE = CApi.SQLITE_OPEN_READWRITE;
|
||||
public static final int OPEN_CREATE = CApi.SQLITE_OPEN_CREATE;
|
||||
public static final int OPEN_EXRESCODE = CApi.SQLITE_OPEN_EXRESCODE;
|
||||
|
||||
public static final int TXN_NONE = CApi.SQLITE_TXN_NONE;
|
||||
public static final int TXN_READ = CApi.SQLITE_TXN_READ;
|
||||
public static final int TXN_WRITE = CApi.SQLITE_TXN_WRITE;
|
||||
@ -107,6 +108,10 @@ public final class Sqlite implements AutoCloseable {
|
||||
/* We elide the UTF16_ALIGNED from this interface because it
|
||||
is irrelevant for the Java interface. */
|
||||
|
||||
public static final int DONE = CApi.SQLITE_DONE;
|
||||
public static final int BUSY = CApi.SQLITE_BUSY;
|
||||
public static final int LOCKED = CApi.SQLITE_LOCKED;
|
||||
|
||||
//! Used only by the open() factory functions.
|
||||
private Sqlite(sqlite3 db){
|
||||
this.db = db;
|
||||
@ -430,10 +435,6 @@ public final class Sqlite implements AutoCloseable {
|
||||
return CApi.sqlite3_get_autocommit(thisDb());
|
||||
}
|
||||
|
||||
public void setBusyTimeout(int ms){
|
||||
checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms));
|
||||
}
|
||||
|
||||
/**
|
||||
Analog to sqlite3_txn_state(). Returns one of TXN_NONE, TXN_READ,
|
||||
or TXN_WRITE to denote this database's current transaction state
|
||||
@ -1028,11 +1029,6 @@ public final class Sqlite implements AutoCloseable {
|
||||
private Sqlite dbTo = null;
|
||||
private Sqlite dbFrom = null;
|
||||
|
||||
|
||||
public static final int DONE = CApi.SQLITE_DONE;
|
||||
public static final int BUSY = CApi.SQLITE_BUSY;
|
||||
public static final int LOCKED = CApi.SQLITE_LOCKED;
|
||||
|
||||
Backup(Sqlite dbDest, String schemaDest,Sqlite dbSrc, String schemaSrc){
|
||||
this.dbTo = dbDest;
|
||||
this.dbFrom = dbSrc;
|
||||
@ -1073,19 +1069,19 @@ public final class Sqlite implements AutoCloseable {
|
||||
|
||||
/**
|
||||
Analog to sqlite3_backup_step(). Returns 0 if stepping succeeds
|
||||
or, DONE if the end is reached, BUSY if one of the databases is
|
||||
busy, LOCKED if one of the databases is locked, and throws for
|
||||
any other result code or if this object has been closed. Note
|
||||
that BUSY and LOCKED are not necessarily permanent errors, so
|
||||
do not trigger an exception.
|
||||
or, Sqlite.DONE if the end is reached, Sqlite.BUSY if one of
|
||||
the databases is busy, Sqlite.LOCKED if one of the databases is
|
||||
locked, and throws for any other result code or if this object
|
||||
has been closed. Note that BUSY and LOCKED are not necessarily
|
||||
permanent errors, so do not trigger an exception.
|
||||
*/
|
||||
public int step(int pageCount){
|
||||
final int rc = CApi.sqlite3_backup_step(getNative(), pageCount);
|
||||
switch(rc){
|
||||
case 0:
|
||||
case DONE:
|
||||
case BUSY:
|
||||
case LOCKED:
|
||||
case Sqlite.DONE:
|
||||
case Sqlite.BUSY:
|
||||
case Sqlite.LOCKED:
|
||||
return rc;
|
||||
default:
|
||||
toss();
|
||||
@ -1219,4 +1215,42 @@ public final class Sqlite implements AutoCloseable {
|
||||
}
|
||||
checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) );
|
||||
}
|
||||
|
||||
/**
|
||||
Callback for use with busyHandler().
|
||||
*/
|
||||
public interface BusyHandler {
|
||||
/**
|
||||
Must function as documented for the C-level
|
||||
sqlite3_busy_handler() callback argument, minus the (void*)
|
||||
argument the C-level function requires.
|
||||
|
||||
If this function throws, it is translated to a database-level
|
||||
error.
|
||||
*/
|
||||
int call(int n);
|
||||
}
|
||||
|
||||
/**
|
||||
Analog to sqlite3_busy_timeout().
|
||||
*/
|
||||
public void setBusyTimeout(int ms){
|
||||
checkRc(CApi.sqlite3_busy_timeout(thisDb(), ms));
|
||||
}
|
||||
|
||||
/**
|
||||
Analog to sqlite3_busy_handler(). If b is null then any
|
||||
current handler is cleared.
|
||||
*/
|
||||
void setBusyHandler( BusyHandler b ){
|
||||
org.sqlite.jni.capi.BusyHandlerCallback bhc = null;
|
||||
if( null!=b ){
|
||||
bhc = new org.sqlite.jni.capi.BusyHandlerCallback(){
|
||||
@Override public int call(int n){
|
||||
return b.call(n);
|
||||
}
|
||||
};
|
||||
}
|
||||
checkRc( CApi.sqlite3_busy_handler(thisDb(), bhc) );
|
||||
}
|
||||
}
|
||||
|
@ -623,7 +623,7 @@ public class Tester2 implements Runnable {
|
||||
try (Sqlite.Backup b = dbDest.initBackup("main",dbSrc,"main")) {
|
||||
affirm( null!=b );
|
||||
int rc;
|
||||
while( Sqlite.Backup.DONE!=(rc = b.step(1)) ){
|
||||
while( Sqlite.DONE!=(rc = b.step(1)) ){
|
||||
affirm( 0==rc );
|
||||
}
|
||||
affirm( b.pageCount() > 0 );
|
||||
@ -699,6 +699,44 @@ public class Tester2 implements Runnable {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@SingleThreadOnly /* because threads inherently break this test */
|
||||
private void testBusy(){
|
||||
final String dbName = "_busy-handler.db";
|
||||
try{
|
||||
Sqlite db1 = openDb(dbName);
|
||||
++metrics.dbOpen;
|
||||
execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)");
|
||||
Sqlite db2 = openDb(dbName);
|
||||
++metrics.dbOpen;
|
||||
|
||||
final ValueHolder<Integer> xBusyCalled = new ValueHolder<>(0);
|
||||
Sqlite.BusyHandler handler = new Sqlite.BusyHandler(){
|
||||
@Override public int call(int n){
|
||||
return n > 2 ? 0 : ++xBusyCalled.value;
|
||||
}
|
||||
};
|
||||
db2.setBusyHandler(handler);
|
||||
|
||||
// Force a locked condition...
|
||||
execSql(db1, "BEGIN EXCLUSIVE");
|
||||
int rc = 0;
|
||||
SqliteException ex = null;
|
||||
try{
|
||||
db2.prepare("SELECT * from t");
|
||||
}catch(SqliteException x){
|
||||
ex = x;
|
||||
}
|
||||
affirm( null!=ex );
|
||||
affirm( Sqlite.BUSY == ex.errcode() );
|
||||
affirm( 3 == xBusyCalled.value );
|
||||
db1.close();
|
||||
db2.close();
|
||||
}finally{
|
||||
try{(new java.io.File(dbName)).delete();}
|
||||
catch(Exception e){/* ignore */}
|
||||
}
|
||||
}
|
||||
|
||||
private void runTests(boolean fromThread) throws Exception {
|
||||
List<java.lang.reflect.Method> mlist = testMethods;
|
||||
affirm( null!=mlist );
|
||||
|
Reference in New Issue
Block a user