1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +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 );

View File

@ -1,5 +1,5 @@
C Ensure\sthat\sthe\sYYYY-MM-DD\sinput\sto\sdate\sand\stime\sfunctions\shas\sbeen\snormalized\nprior\sto\sreturning\sa\sresult.\n[forum:/forumpost/6bb476897e|Forum\spost\s6bb476897e].
D 2023-11-04T21:44:00.659
C Reimplement\sauto-extensions\sin\sJava\sfor\suse\swith\sthe\sJNI\swrapper1\sAPI.
D 2023-11-04T21:51:34.589
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/capi/AggregateFunction.java 0b72cdff61533b564d65b63
F ext/jni/src/org/sqlite/jni/capi/AuthorizerCallback.java 7ed409d5449684616cc924534e22ff6b07d361f12ad904b69ecb10e0568a8013
F ext/jni/src/org/sqlite/jni/capi/AutoExtensionCallback.java 74cc4998a73d6563542ecb90804a3c4f4e828cb4bd69e61226d1a51f4646e759
F ext/jni/src/org/sqlite/jni/capi/BusyHandlerCallback.java 7b8e19810c42b0ad21a04b5d8c804b32ee5905d137148703f16a75b612c380ca
F ext/jni/src/org/sqlite/jni/capi/CApi.java d21e6c1c4557ae18bbc2eefb0882efdb36fdaecdc58823c142def994327a365b
F ext/jni/src/org/sqlite/jni/capi/CApi.java 4043d709626079cce6d524ef49122b934c043022bd88bc1e72eb697ac8df86e7
F ext/jni/src/org/sqlite/jni/capi/CallbackProxy.java 57e2d275dcebe690b1fc1f3d34eb96879b2d7039bce30b563aee547bf45d8a8b
F ext/jni/src/org/sqlite/jni/capi/CollationCallback.java e29bcfc540fdd343e2f5cca4d27235113f2886acb13380686756d5cabdfd065a
F ext/jni/src/org/sqlite/jni/capi/CollationNeededCallback.java f81cf10b79c52f9b2e9247d523d29ae48863935f60420eae35f257c38c80ce95
@ -269,7 +269,7 @@ F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java 0d1e9afc9ff8a2adb94a155b72385
F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1
F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java 93b9700fca4c68075ccab12fe0fbbc76c91cafc9f368e835b9bd7cd7732c8615
F ext/jni/src/org/sqlite/jni/capi/TableColumnMetadata.java addf120e0e76e5be1ff2260daa7ce305ff9b5fafd64153a7a28e9d8f000a815f
F ext/jni/src/org/sqlite/jni/capi/Tester1.java 41e2b910a11dfdd4cc39ab608492d7c12f3791e85ac7f9d75d5445f7645a5e57
F ext/jni/src/org/sqlite/jni/capi/Tester1.java 96c27ae10ec44ce5f6a150e8bc6525d86ab2d9118da18649943a0bf4d8d206ce
F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
F ext/jni/src/org/sqlite/jni/capi/UpdateHookCallback.java 2766b8526bbffc4f1045f70e79f1bc1b1efe1c3e95ca06cdb8a7391032dda3b4
F ext/jni/src/org/sqlite/jni/capi/ValueHolder.java 22d365746a78c5cd7ae10c39444eb7bbf1a819aad4bb7eb77b1edc47773a3950
@ -295,10 +295,10 @@ F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ad
F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
F ext/jni/src/org/sqlite/jni/wrapper1/AggregateFunction.java d5c108b02afd3c63c9e5e53f71f85273c1bfdc461ae526e0a0bb2b25e4df6483
F ext/jni/src/org/sqlite/jni/wrapper1/ScalarFunction.java 43c43adfb7866098aadaaca1620028a6ec82d5193149970019b1cce9eb59fb03
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java e787f5f36d5832fe3c7a000a8609eb0629fb160b95f8f25566df13e72e6f5470
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 3e813aa4a680948a1885a5df1537c9245b3b7362aaf6aa31f679640e81da020e
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 2833afdb9af5c1949bb35f4c926a5351fba9d1cdf0996864caa7b47827a346c7
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 3da22cb18d8544fff1c7184aeaa2516c20d63e8a31db848eb7470ce285b284dc
F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 929a1e2ab4e135fbbae7f0d2d609f77cfbbc60bbec7ba789ce23d9c73bc6156e
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 96d7908da8bad591aff8f192cb83e038fd5861ef4601726eeda24905422718c9
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 03638a1774a95bcc7b5de440a5f1398720460e30fc480032a2e8be24e997d30c
F ext/jni/src/org/sqlite/jni/wrapper1/ValueHolder.java 7b89a7391f771692c5b83b0a5b86266abe8d59f1c77d7a0eccc9b79f259d79af
F ext/jni/src/org/sqlite/jni/wrapper1/WindowFunction.java c7d1452f9ff26175b3c19bbf273116cc2846610af68e01756d755f037fe7319f
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
@ -2142,8 +2142,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 c23123af7d40dea24a0848dff987fd58a6703ce04165060533544db85983d566
R 0a52dbd298d991529b412673e9dc7eb5
U drh
Z 33b6056e932d878d8676b163d8b94db2
P b692eb8ccb2d0645599ad73a8bdacf5df499114244aadeb38aabc580fc4dc7c5
R 99817a8518af3f567a07823f0172a1fb
U stephan
Z 276f4a4bca9307a54b6144f3c8e4f323
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
b692eb8ccb2d0645599ad73a8bdacf5df499114244aadeb38aabc580fc4dc7c5
14ed4c64533622e5faf1aaa59c24885885aad43f1c0d4717773e79440e8e1468