1
0
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:
stephan
2023-11-05 00:02:47 +00:00
parent 15d38c0dde
commit b02ca781ad
5 changed files with 140 additions and 69 deletions

View File

@ -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 */}
}
}

View File

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

View File

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