JNI: add scalar UDF support to the wrapper1 API.
FossilOrigin-Name: a850535766d2243d9475e1523c753615875a2da9c9d82a41a9fb61b141c6334a
This commit is contained in:
parent
f2d7e961d9
commit
626d0a9fda
@ -27,7 +27,7 @@ public abstract class ScalarFunction implements SQLFunction {
|
||||
|
||||
/**
|
||||
Optionally override to be notified when the UDF is finalized by
|
||||
SQLite. This implementation does nothing.
|
||||
SQLite. This default implementation does nothing.
|
||||
*/
|
||||
public void xDestroy() {}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ public interface SqlFunction {
|
||||
public final static class Arguments implements Iterable<SqlFunction.Arguments.Arg>{
|
||||
private final sqlite3_context cx;
|
||||
private final sqlite3_value args[];
|
||||
public final int length;
|
||||
|
||||
/**
|
||||
Must be passed the context and arguments for the UDF call this
|
||||
@ -41,6 +42,7 @@ public interface SqlFunction {
|
||||
Arguments(@NotNull sqlite3_context cx, @NotNull sqlite3_value args[]){
|
||||
this.cx = cx;
|
||||
this.args = args;
|
||||
this.length = args.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,6 +114,11 @@ public interface SqlFunction {
|
||||
}
|
||||
|
||||
public int getType(int arg){return CApi.sqlite3_value_type(valueAt(arg));}
|
||||
public int getSubtype(int arg){return CApi.sqlite3_value_subtype(valueAt(arg));}
|
||||
public int getNumericType(int arg){return CApi.sqlite3_value_numeric_type(valueAt(arg));}
|
||||
public int getNoChange(int arg){return CApi.sqlite3_value_nochange(valueAt(arg));}
|
||||
public boolean getFromBind(int arg){return CApi.sqlite3_value_frombind(valueAt(arg));}
|
||||
public int getEncoding(int arg){return CApi.sqlite3_value_encoding(valueAt(arg));}
|
||||
|
||||
public void resultInt(int v){ CApi.sqlite3_result_int(cx, v); }
|
||||
public void resultInt64(long v){ CApi.sqlite3_result_int64(cx, v); }
|
||||
@ -121,6 +128,7 @@ public interface SqlFunction {
|
||||
public void resultErrorTooBig(){CApi.sqlite3_result_error_toobig(cx);}
|
||||
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);}
|
||||
public void resultArg(int argNdx){CApi.sqlite3_result_value(cx, valueAt(argNdx));}
|
||||
public void resultZeroBlob(long n){
|
||||
// Throw on error? If n is too big,
|
||||
@ -150,4 +158,27 @@ public interface SqlFunction {
|
||||
return CApi.sqlite3_get_auxdata(cx, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Internal-use adapter for wrapping this package's ScalarFunction
|
||||
for use with the org.sqlite.jni.capi.ScalarFunction interface.
|
||||
*/
|
||||
static final class ScalarAdapter extends org.sqlite.jni.capi.ScalarFunction {
|
||||
final ScalarFunction impl;
|
||||
ScalarAdapter(ScalarFunction impl){
|
||||
this.impl = impl;
|
||||
}
|
||||
/**
|
||||
Proxies this.f.xFunc(), adapting the call arguments to that
|
||||
function's signature.
|
||||
*/
|
||||
public void xFunc(sqlite3_context cx, sqlite3_value[] args){
|
||||
impl.xFunc( new SqlFunction.Arguments(cx, args) );
|
||||
}
|
||||
|
||||
public void xDestroy(){
|
||||
impl.xDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
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;
|
||||
import org.sqlite.jni.capi.OutputPointer;
|
||||
@ -71,14 +72,16 @@ public final class Sqlite implements AutoCloseable {
|
||||
|
||||
/**
|
||||
Returns this object's underlying native db handle, or null if
|
||||
this instance has been closed.
|
||||
this instance has been closed. This is very specifically not
|
||||
public.
|
||||
*/
|
||||
sqlite3 nativeHandle(){ return this.db; }
|
||||
|
||||
private void affirmOpen(){
|
||||
private sqlite3 affirmOpen(){
|
||||
if( null==db || 0==db.getNativePointer() ){
|
||||
throw new IllegalArgumentException("This database instance is closed.");
|
||||
}
|
||||
return this.db;
|
||||
}
|
||||
|
||||
// private byte[] stringToUtf8(String s){
|
||||
@ -91,6 +94,10 @@ public final class Sqlite implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Corresponds to the sqlite3_stmt class. Use Sqlite.prepare() to
|
||||
create new instances.
|
||||
*/
|
||||
public final class Stmt implements AutoCloseable {
|
||||
private Sqlite _db = null;
|
||||
private sqlite3_stmt stmt = null;
|
||||
@ -114,7 +121,7 @@ public final class Sqlite implements AutoCloseable {
|
||||
/**
|
||||
Corresponds to sqlite3_finalize(), but we cannot override the
|
||||
name finalize() here because this one requires a different
|
||||
signature. We do not throw on error here because "destructors
|
||||
signature. It does not throw on error here because "destructors
|
||||
do not throw." If it returns non-0, the object is still
|
||||
finalized.
|
||||
*/
|
||||
@ -178,9 +185,8 @@ public final class Sqlite implements AutoCloseable {
|
||||
rather than the sqlite3_stmt class.
|
||||
*/
|
||||
public Stmt prepare(String sql, int prepFlags){
|
||||
affirmOpen();
|
||||
final OutputPointer.sqlite3_stmt out = new OutputPointer.sqlite3_stmt();
|
||||
final int rc = sqlite3_prepare_v3(this.db, sql, prepFlags, out);
|
||||
final int rc = sqlite3_prepare_v3(affirmOpen(), sql, prepFlags, out);
|
||||
affirmRcOk(rc);
|
||||
return new Stmt(this, out.take());
|
||||
}
|
||||
@ -189,4 +195,15 @@ public final class Sqlite implements AutoCloseable {
|
||||
return prepare(sql, 0);
|
||||
}
|
||||
|
||||
|
||||
public void createFunction(String name, int nArg, int eTextRep, ScalarFunction f ){
|
||||
int rc = CApi.sqlite3_create_function(affirmOpen(), name, nArg, eTextRep,
|
||||
new SqlFunction.ScalarAdapter(f));
|
||||
if( 0!=rc ) throw new SqliteException(db);
|
||||
}
|
||||
|
||||
public void createFunction(String name, int nArg, ScalarFunction f){
|
||||
this.createFunction(name, nArg, CApi.SQLITE_UTF8, f);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,17 @@ import org.sqlite.jni.capi.*;
|
||||
@java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD})
|
||||
@interface SingleThreadOnly{}
|
||||
|
||||
/**
|
||||
A helper class which simply holds a single value. Its current use
|
||||
is for communicating values out of anonymous classes, as doing so
|
||||
requires a "final" reference.
|
||||
*/
|
||||
class ValueHolder<T> {
|
||||
public T value;
|
||||
public ValueHolder(){}
|
||||
public ValueHolder(T v){value = v;}
|
||||
}
|
||||
|
||||
public class Tester2 implements Runnable {
|
||||
//! True when running in multi-threaded mode.
|
||||
private static boolean mtMode = false;
|
||||
@ -124,6 +135,56 @@ public class Tester2 implements Runnable {
|
||||
affirm(v, "Affirmation failed.");
|
||||
}
|
||||
|
||||
|
||||
public static void execSql(Sqlite db, String[] sql){
|
||||
execSql(db, String.join("", sql));
|
||||
}
|
||||
|
||||
public static int execSql(Sqlite dbw, boolean throwOnError, String sql){
|
||||
final sqlite3 db = dbw.nativeHandle();
|
||||
OutputPointer.Int32 oTail = new OutputPointer.Int32();
|
||||
final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
|
||||
int pos = 0, n = 1;
|
||||
byte[] sqlChunk = sqlUtf8;
|
||||
int rc = 0;
|
||||
sqlite3_stmt stmt = null;
|
||||
final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt();
|
||||
while(pos < sqlChunk.length){
|
||||
if(pos > 0){
|
||||
sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
|
||||
sqlChunk.length);
|
||||
}
|
||||
if( 0==sqlChunk.length ) break;
|
||||
rc = CApi.sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail);
|
||||
if(throwOnError) affirm(0 == rc);
|
||||
else if( 0!=rc ) break;
|
||||
pos = oTail.value;
|
||||
stmt = outStmt.take();
|
||||
if( null == stmt ){
|
||||
// empty statement was parsed.
|
||||
continue;
|
||||
}
|
||||
affirm(0 != stmt.getNativePointer());
|
||||
while( CApi.SQLITE_ROW == (rc = CApi.sqlite3_step(stmt)) ){
|
||||
}
|
||||
CApi.sqlite3_finalize(stmt);
|
||||
affirm(0 == stmt.getNativePointer());
|
||||
if(0!=rc && CApi.SQLITE_ROW!=rc && CApi.SQLITE_DONE!=rc){
|
||||
break;
|
||||
}
|
||||
}
|
||||
CApi.sqlite3_finalize(stmt);
|
||||
if(CApi.SQLITE_ROW==rc || CApi.SQLITE_DONE==rc) rc = 0;
|
||||
if( 0!=rc && throwOnError){
|
||||
throw new SqliteException(db);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void execSql(Sqlite db, String sql){
|
||||
execSql(db, true, sql);
|
||||
}
|
||||
|
||||
@SingleThreadOnly /* because it's thread-agnostic */
|
||||
private void test1(){
|
||||
affirm(CApi.sqlite3_libversion_number() == CApi.SQLITE_VERSION_NUMBER);
|
||||
@ -144,9 +205,11 @@ public class Tester2 implements Runnable {
|
||||
}
|
||||
|
||||
Sqlite openDb(String name){
|
||||
return Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE|
|
||||
CApi.SQLITE_OPEN_CREATE|
|
||||
CApi.SQLITE_OPEN_EXRESCODE);
|
||||
final Sqlite db = Sqlite.open(name, CApi.SQLITE_OPEN_READWRITE|
|
||||
CApi.SQLITE_OPEN_CREATE|
|
||||
CApi.SQLITE_OPEN_EXRESCODE);
|
||||
++metrics.dbOpen;
|
||||
return db;
|
||||
}
|
||||
|
||||
Sqlite openDb(){ return openDb(":memory:"); }
|
||||
@ -190,6 +253,32 @@ public class Tester2 implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
void testUdfScalar(){
|
||||
final ValueHolder<Integer> xDestroyCalled = new ValueHolder<>(0);
|
||||
try (Sqlite db = openDb()) {
|
||||
execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)");
|
||||
final ValueHolder<Integer> vh = new ValueHolder<>(0);
|
||||
final ScalarFunction f = new ScalarFunction(){
|
||||
public void xFunc(SqlFunction.Arguments args){
|
||||
for( SqlFunction.Arguments.Arg arg : args ){
|
||||
vh.value += arg.getInt();
|
||||
}
|
||||
}
|
||||
public void xDestroy(){
|
||||
++xDestroyCalled.value;
|
||||
}
|
||||
};
|
||||
db.createFunction("myfunc", -1, f);
|
||||
execSql(db, "select myfunc(1,2,3)");
|
||||
affirm( 6 == vh.value );
|
||||
vh.value = 0;
|
||||
execSql(db, "select myfunc(-1,-2,-3)");
|
||||
affirm( -6 == vh.value );
|
||||
affirm( 0 == xDestroyCalled.value );
|
||||
}
|
||||
affirm( 1 == xDestroyCalled.value );
|
||||
}
|
||||
|
||||
private void runTests(boolean fromThread) throws Exception {
|
||||
List<java.lang.reflect.Method> mlist = testMethods;
|
||||
affirm( null!=mlist );
|
||||
|
18
manifest
18
manifest
@ -1,5 +1,5 @@
|
||||
C JNI:\sinitial\sdraft\s(untested\s-\srequires\smore\sinfrastructure\sfirst)\sof\sa\sUDF\sargument/result-handling\sinterface\swhich\scompletely\shides\sthe\sC-style\sAPI\sfrom\sthe\sclient.
|
||||
D 2023-10-16T13:04:42.394
|
||||
C JNI:\sadd\sscalar\sUDF\ssupport\sto\sthe\swrapper1\sAPI.
|
||||
D 2023-10-16T14:31:13.824
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -264,7 +264,7 @@ F ext/jni/src/org/sqlite/jni/capi/ResultCode.java 8141171f1bcf9f46eef303b9d3c5dc
|
||||
F ext/jni/src/org/sqlite/jni/capi/RollbackHookCallback.java 105e324d09c207100485e7667ad172e64322c62426bb49b547e9b0dc9c33f5f0
|
||||
F ext/jni/src/org/sqlite/jni/capi/SQLFunction.java fef556adbc3624292423083a648bdf97fa8a4f6b3b6577c9660dd7bd6a6d3c4a
|
||||
F ext/jni/src/org/sqlite/jni/capi/SQLTester.java 09bee15aa0eedac68d767ae21d9a6a62a31ade59182a3ccbf036d6463d9e30b1
|
||||
F ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java dee85ef2650a9c95067f5d55bd6e290e0404e6643a5d115d1a1533df21f9b5c8
|
||||
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 8aacea90b0eed6e4e801cfba2515a66b5d602e124f1ba68fe3d2f0aa98f0f443
|
||||
F ext/jni/src/org/sqlite/jni/capi/TraceV2Callback.java 0a25e117a0daae3394a77f24713e36d7b44c67d6e6d30e9e1d56a63442eef723
|
||||
@ -289,10 +289,10 @@ F ext/jni/src/org/sqlite/jni/fts5/fts5_api.java a8e88c3783d21cec51b0748568a96653
|
||||
F ext/jni/src/org/sqlite/jni/fts5/fts5_extension_function.java 9e2b954d210d572552b28aca523b272fae14bd41e318921b22f65b728d5bf978
|
||||
F ext/jni/src/org/sqlite/jni/fts5/fts5_tokenizer.java 92bdaa3893bd684533004d64ade23d329843f809cd0d0f4f1a2856da6e6b4d90
|
||||
F ext/jni/src/org/sqlite/jni/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 40a9f4f8a7a72b90b12baa82d26ba16376a5758009739b069c1863201770e816
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 2bc90edc4c25225e018ed600b5eff43ba0485be85db08f8b6b35246372fdac20
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java 8b422ec8a2e922c1c21db549e68e0eb93078d2c4d341354043975e111a43b10d
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java 7826de9bea3102d8a2ecaef3cc84480d8d6f6bc617c531d2078b419913c866fd
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/SqliteException.java 1386f7b753134fc12253ce2fbbc448ba8c970567fac01a3356cb672e14408d73
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java aee8301f92256ab8572043cf5de2a35afda057d2a6ff09970a2f84a90305471e
|
||||
F ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java 3dccdb259bd1d737c6d104bdf488fb489063b40a113c03b311284e0287d0d5b7
|
||||
F ext/jni/src/tests/000-000-sanity.test c3427a0e0ac84d7cbe4c95fdc1cd4b61f9ddcf43443408f3000139478c4dc745
|
||||
F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70
|
||||
F ext/jni/src/tests/900-001-fts.test bf0ce17a8d082773450e91f2388f5bbb2dfa316d0b676c313c637a91198090f0
|
||||
@ -2129,8 +2129,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 abc82bf4b800dde1b6e6172c7be816edb391fdbed5dbd2749f54623fdf3bf8e6
|
||||
R 287c21329f3772a974832101be3bffee
|
||||
P 43b10a5cf9cb8be53d62914f340d533e60a70bf4caa8b9b91c0f867fa0f70493
|
||||
R b476ec63940a46ee7b1e2c0e07bbcf7b
|
||||
U stephan
|
||||
Z a4e77b564ffa0b3970c5e65a1253c53d
|
||||
Z a39099e216c3c59927f5c5a18a7e93ef
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
@ -1 +1 @@
|
||||
43b10a5cf9cb8be53d62914f340d533e60a70bf4caa8b9b91c0f867fa0f70493
|
||||
a850535766d2243d9475e1523c753615875a2da9c9d82a41a9fb61b141c6334a
|
Loading…
Reference in New Issue
Block a user