From: stephan Date: Mon, 16 Oct 2023 14:31:13 +0000 (+0000) Subject: JNI: add scalar UDF support to the wrapper1 API. X-Git-Tag: version-3.44.0~103 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=626d0a9fda410efcf405ed8bb6f7b00acee80207;p=thirdparty%2Fsqlite.git JNI: add scalar UDF support to the wrapper1 API. FossilOrigin-Name: a850535766d2243d9475e1523c753615875a2da9c9d82a41a9fb61b141c6334a --- diff --git a/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java b/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java index f517b3610c..95541bdcba 100644 --- a/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java +++ b/ext/jni/src/org/sqlite/jni/capi/ScalarFunction.java @@ -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() {} } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java index 4be6dcbe23..6851bf8379 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/SqlFunction.java @@ -33,6 +33,7 @@ public interface SqlFunction { public final static class Arguments implements Iterable{ 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(); + } + } + } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java index b6a4bda108..7321d7bb36 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Sqlite.java @@ -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); + } + } diff --git a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java index 73f07b1561..07a43b63dd 100644 --- a/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java +++ b/ext/jni/src/org/sqlite/jni/wrapper1/Tester2.java @@ -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 { + 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 xDestroyCalled = new ValueHolder<>(0); + try (Sqlite db = openDb()) { + execSql(db, "create table t(a); insert into t(a) values(1),(2),(3)"); + final ValueHolder 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 mlist = testMethods; affirm( null!=mlist ); diff --git a/manifest b/manifest index 978dd1cded..1fb9230659 100644 --- a/manifest +++ b/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. diff --git a/manifest.uuid b/manifest.uuid index 2eac8e0f45..0ec7f3375d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -43b10a5cf9cb8be53d62914f340d533e60a70bf4caa8b9b91c0f867fa0f70493 \ No newline at end of file +a850535766d2243d9475e1523c753615875a2da9c9d82a41a9fb61b141c6334a \ No newline at end of file