1
0
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:
stephan
2023-11-04 21:51:34 +00:00
parent d4677f192f
commit ffdb479e7c
7 changed files with 194 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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