mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Reimplement auto-extensions in Java for use with the JNI wrapper1 API.
FossilOrigin-Name: 14ed4c64533622e5faf1aaa59c24885885aad43f1c0d4717773e79440e8e1468
This commit is contained in:
@ -891,7 +891,7 @@ public final class CApi {
|
||||
}
|
||||
|
||||
public static native boolean sqlite3_extended_result_codes(
|
||||
@NotNull sqlite3 db, boolean onoff
|
||||
@NotNull sqlite3 db, boolean on
|
||||
);
|
||||
|
||||
static native boolean sqlite3_get_autocommit(@NotNull long ptrToDb);
|
||||
|
@ -1419,7 +1419,7 @@ public class Tester1 implements Runnable {
|
||||
|
||||
val.value = 0;
|
||||
final AutoExtensionCallback ax2 = new AutoExtensionCallback(){
|
||||
@Override public synchronized int call(sqlite3 db){
|
||||
@Override public int call(sqlite3 db){
|
||||
++val.value;
|
||||
return 0;
|
||||
}
|
||||
|
@ -117,6 +117,10 @@ public interface SqlFunction {
|
||||
public void resultErrorCode(int rc){CApi.sqlite3_result_error_code(cx, rc);}
|
||||
public void resultObject(Object o){CApi.sqlite3_result_java_object(cx, o);}
|
||||
public void resultNull(){CApi.sqlite3_result_null(cx);}
|
||||
/**
|
||||
Analog to sqlite3_result_value(), using the Value object at the
|
||||
given argument index.
|
||||
*/
|
||||
public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
|
||||
public void resultSubtype(int subtype){CApi.sqlite3_result_subtype(cx, subtype);}
|
||||
public void resultZeroBlob(long n){
|
||||
|
@ -13,7 +13,6 @@
|
||||
*/
|
||||
package org.sqlite.jni.wrapper1;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import static org.sqlite.jni.capi.CApi.*;
|
||||
import org.sqlite.jni.capi.CApi;
|
||||
import org.sqlite.jni.capi.sqlite3;
|
||||
import org.sqlite.jni.capi.sqlite3_stmt;
|
||||
@ -129,7 +128,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
*/
|
||||
public static Sqlite open(String filename, int flags, String vfsName){
|
||||
final OutputPointer.sqlite3 out = new OutputPointer.sqlite3();
|
||||
final int rc = sqlite3_open_v2(filename, out, flags, vfsName);
|
||||
final int rc = CApi.sqlite3_open_v2(filename, out, flags, vfsName);
|
||||
final sqlite3 n = out.take();
|
||||
if( 0!=rc ){
|
||||
if( null==n ) throw new SqliteException(rc);
|
||||
@ -137,10 +136,11 @@ public final class Sqlite implements AutoCloseable {
|
||||
n.close();
|
||||
throw ex;
|
||||
}
|
||||
Sqlite rv = new Sqlite(n);
|
||||
final Sqlite rv = new Sqlite(n);
|
||||
synchronized(nativeToWrapper){
|
||||
nativeToWrapper.put(n, rv);
|
||||
}
|
||||
runAutoExtensions(rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
}
|
||||
|
||||
public static Sqlite open(String filename){
|
||||
return open(filename, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, null);
|
||||
return open(filename, OPEN_READWRITE|OPEN_CREATE, null);
|
||||
}
|
||||
|
||||
public static String libVersion(){
|
||||
@ -308,7 +308,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
if( 0!=rc ){
|
||||
if( CApi.SQLITE_NOMEM==rc ){
|
||||
throw new OutOfMemoryError();
|
||||
}else if( null==db || 0==sqlite3_errcode(db)){
|
||||
}else if( null==db || 0==CApi.sqlite3_errcode(db)){
|
||||
throw new SqliteException(rc);
|
||||
}else{
|
||||
throw new SqliteException(db);
|
||||
@ -343,7 +343,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
*/
|
||||
public Stmt prepare(String sql, int prepFlags){
|
||||
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
|
||||
final int rc = sqlite3_prepare_v3(thisDb(), sql, prepFlags, out);
|
||||
final int rc = CApi.sqlite3_prepare_v3(thisDb(), sql, prepFlags, out);
|
||||
checkRc(rc);
|
||||
final sqlite3_stmt q = out.take();
|
||||
if( null==q ){
|
||||
@ -724,7 +724,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
synchronized(nativeToWrapper){
|
||||
nativeToWrapper.remove(this.stmt);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
CApi.sqlite3_finalize(stmt);
|
||||
stmt = null;
|
||||
_db = null;
|
||||
resultColCount = 0;
|
||||
@ -745,8 +745,8 @@ public final class Sqlite implements AutoCloseable {
|
||||
private int checkRc(int rc){
|
||||
switch(rc){
|
||||
case 0:
|
||||
case SQLITE_ROW:
|
||||
case SQLITE_DONE: return rc;
|
||||
case CApi.SQLITE_ROW:
|
||||
case CApi.SQLITE_DONE: return rc;
|
||||
default:
|
||||
if( null==stmt ) throw new SqliteException(rc);
|
||||
else throw new SqliteException(this);
|
||||
@ -759,7 +759,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
result.
|
||||
*/
|
||||
public boolean step(){
|
||||
switch(checkRc(sqlite3_step(thisStmt()))){
|
||||
switch(checkRc(CApi.sqlite3_step(thisStmt()))){
|
||||
case CApi.SQLITE_ROW: return true;
|
||||
case CApi.SQLITE_DONE: return false;
|
||||
default:
|
||||
@ -930,4 +930,93 @@ public final class Sqlite implements AutoCloseable {
|
||||
}
|
||||
} /* Stmt class */
|
||||
|
||||
/**
|
||||
Interface for auto-extensions, as per the
|
||||
sqlite3_auto_extension() API.
|
||||
|
||||
Design note: the chicken/egg timing of auto-extension execution
|
||||
requires that this feature be entirely re-implemented in Java
|
||||
because the C-level API has no access to the Sqlite type so
|
||||
cannot pass on an object of that type while the database is being
|
||||
opened. One side effect of this reimplementation is that this
|
||||
class's list of auto-extensions is 100% independent of the
|
||||
C-level list so, e.g., clearAutoExtensions() will have no effect
|
||||
on auto-extensions added via the C-level API and databases opened
|
||||
from that level of API will not be passed to this level's
|
||||
AutoExtension instances.
|
||||
*/
|
||||
public interface AutoExtension {
|
||||
public void call(Sqlite db);
|
||||
}
|
||||
|
||||
private static final java.util.Set<AutoExtension> autoExtensions =
|
||||
new java.util.LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
Passes db to all auto-extensions. If any one of them throws,
|
||||
db.close() is called before the exception is propagated.
|
||||
*/
|
||||
private static void runAutoExtensions(Sqlite db){
|
||||
AutoExtension list[];
|
||||
synchronized(autoExtensions){
|
||||
/* Avoid that modifications to the AutoExtension list from within
|
||||
auto-extensions affect this execution of this list. */
|
||||
list = autoExtensions.toArray(new AutoExtension[0]);
|
||||
}
|
||||
try {
|
||||
for( AutoExtension ax : list ) ax.call(db);
|
||||
}catch(Exception e){
|
||||
db.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Analog to sqlite3_auto_extension(), adds the given object to the
|
||||
list of auto-extensions if it is not already in that list. The
|
||||
given object will be run as part of Sqlite.open(), and passed the
|
||||
being-opened database. If the extension throws then open() will
|
||||
fail.
|
||||
|
||||
This API does not guaranty whether or not manipulations made to
|
||||
the auto-extension list from within auto-extension callbacks will
|
||||
affect the current traversal of the auto-extension list. Whether
|
||||
or not they do is unspecified and subject to change between
|
||||
versions. e.g. if an AutoExtension calls addAutoExtension(),
|
||||
whether or not the new extension will be run on the being-opened
|
||||
database is undefined.
|
||||
|
||||
Note that calling Sqlite.open() from an auto-extension will
|
||||
necessarily result in recursion loop and (eventually) a stack
|
||||
overflow.
|
||||
*/
|
||||
public static void addAutoExtension( AutoExtension e ){
|
||||
if( null==e ){
|
||||
throw new IllegalArgumentException("AutoExtension may not be null.");
|
||||
}
|
||||
synchronized(autoExtensions){
|
||||
autoExtensions.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Removes the given object from the auto-extension list if it is in
|
||||
that list, otherwise this has no side-effects beyond briefly
|
||||
locking that list.
|
||||
*/
|
||||
public static void removeAutoExtension( AutoExtension e ){
|
||||
synchronized(autoExtensions){
|
||||
autoExtensions.remove(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Removes all auto-extensions which were added via addAutoExtension().
|
||||
*/
|
||||
public static void clearAutoExtensions(){
|
||||
synchronized(autoExtensions){
|
||||
autoExtensions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -526,6 +526,83 @@ public class Tester2 implements Runnable {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@SingleThreadOnly /* because multiple threads legitimately make these
|
||||
results unpredictable */
|
||||
private synchronized void testAutoExtension(){
|
||||
final ValueHolder<Integer> val = new ValueHolder<>(0);
|
||||
final ValueHolder<String> toss = new ValueHolder<>(null);
|
||||
final Sqlite.AutoExtension ax = new Sqlite.AutoExtension(){
|
||||
@Override public void call(Sqlite db){
|
||||
++val.value;
|
||||
if( null!=toss.value ){
|
||||
throw new RuntimeException(toss.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
Sqlite.addAutoExtension(ax);
|
||||
openDb().close();
|
||||
affirm( 1==val.value );
|
||||
openDb().close();
|
||||
affirm( 2==val.value );
|
||||
Sqlite.clearAutoExtensions();
|
||||
openDb().close();
|
||||
affirm( 2==val.value );
|
||||
|
||||
Sqlite.addAutoExtension( ax );
|
||||
Sqlite.addAutoExtension( ax ); // Must not add a second entry
|
||||
Sqlite.addAutoExtension( ax ); // or a third one
|
||||
openDb().close();
|
||||
affirm( 3==val.value );
|
||||
|
||||
Sqlite db = openDb();
|
||||
affirm( 4==val.value );
|
||||
execSql(db, "ATTACH ':memory:' as foo");
|
||||
affirm( 4==val.value, "ATTACH uses the same connection, not sub-connections." );
|
||||
db.close();
|
||||
db = null;
|
||||
|
||||
Sqlite.removeAutoExtension(ax);
|
||||
openDb().close();
|
||||
affirm( 4==val.value );
|
||||
Sqlite.addAutoExtension(ax);
|
||||
Exception err = null;
|
||||
toss.value = "Throwing from auto_extension.";
|
||||
try{
|
||||
openDb();
|
||||
}catch(Exception e){
|
||||
err = e;
|
||||
}
|
||||
affirm( err!=null );
|
||||
affirm( err.getMessage().indexOf(toss.value)>=0 );
|
||||
toss.value = null;
|
||||
|
||||
val.value = 0;
|
||||
final Sqlite.AutoExtension ax2 = new Sqlite.AutoExtension(){
|
||||
@Override public void call(Sqlite db){
|
||||
++val.value;
|
||||
}
|
||||
};
|
||||
Sqlite.addAutoExtension(ax2);
|
||||
openDb().close();
|
||||
affirm( 2 == val.value );
|
||||
Sqlite.removeAutoExtension(ax);
|
||||
openDb().close();
|
||||
affirm( 3 == val.value );
|
||||
Sqlite.addAutoExtension(ax);
|
||||
openDb().close();
|
||||
affirm( 5 == val.value );
|
||||
Sqlite.removeAutoExtension(ax2);
|
||||
openDb().close();
|
||||
affirm( 6 == val.value );
|
||||
Sqlite.addAutoExtension(ax2);
|
||||
openDb().close();
|
||||
affirm( 8 == val.value );
|
||||
|
||||
Sqlite.clearAutoExtensions();
|
||||
openDb().close();
|
||||
affirm( 8 == val.value );
|
||||
}
|
||||
|
||||
private void runTests(boolean fromThread) throws Exception {
|
||||
List<java.lang.reflect.Method> mlist = testMethods;
|
||||
affirm( null!=mlist );
|
||||
|
Reference in New Issue
Block a user