From ecceaa8b3224897fe8e7db71c98ba3c1dcd3db0b Mon Sep 17 00:00:00 2001 From: asmhatre Date: Fri, 30 Oct 2026 16:36:09 +0000 Subject: [PATCH] Add jextract module: SQLite bindings generated with jextract, FFM arenas, C strings, out-parameters, errors, upcalls, and a JMH comparison with JNI jextract-25 early-access build (25-jextract+2-4) on JDK 25.0.5+8 against SQLite 3.45.1: 25 functions and 13 constants generated into 4 committed files (the whole header would be 83 files), a small Db wrapper, the four arena kinds used wrongly on purpose, ADDRESS vs C_POINTER zero-length vs unbounded segments and the SIGSEGV from getString on a NULL pointer, sqlite3_open out-parameter, return codes to exceptions, a sqlite3_exec upcall and the VM exit when it throws, the JDK 25 native-access warning and --illegal-native-access=deny, library loading failures, and a JMH benchmark of FFM vs hand-written JNI vs plain Java (indicative only: shared 2-CPU machine). Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X --- README.md | 1 + jextract/.gitignore | 4 + jextract/README.md | 54 + jextract/bench/src/bench/SqliteBench.java | 98 + .../demo/sqlite/sqlite3_bind_text$x0.java | 69 + .../demo/sqlite/sqlite3_exec$callback.java | 73 + .../demo/sqlite/sqlite3_h$shared.java | 63 + jextract/generated/demo/sqlite/sqlite3_h.java | 1662 +++++++++++++++++ jextract/jni/SqliteJni.java | 22 + jextract/jni/sqlite_jni.c | 69 + jextract/output/01-tools.txt | 16 + jextract/output/02-generate.txt | 26 + jextract/output/03-hello.txt | 14 + jextract/output/04-native-access.txt | 18 + jextract/output/05-tour.txt | 6 + jextract/output/06-arenas.txt | 21 + jextract/output/07-pointer-traps.txt | 18 + jextract/output/08-errors.txt | 8 + jextract/output/09-callbacks.txt | 13 + jextract/output/10-jmh.txt | 14 + jextract/output/11-library-loading.txt | 14 + jextract/scripts/build-jni.sh | 11 + jextract/scripts/fetch-jextract.sh | 10 + jextract/scripts/fetch-jmh.sh | 15 + jextract/scripts/gen.sh | 24 + jextract/scripts/run-all.sh | 81 + jextract/scripts/run-jmh.sh | 23 + jextract/src/demo/Arenas.java | 65 + jextract/src/demo/Callbacks.java | 43 + jextract/src/demo/Db.java | 155 ++ jextract/src/demo/Errors.java | 29 + jextract/src/demo/Hello.java | 16 + jextract/src/demo/NullText.java | 28 + jextract/src/demo/SqliteException.java | 15 + jextract/src/demo/Tour.java | 28 + jextract/src/demo/Trap.java | 33 + jextract/src/extra/V.java | 8 + 37 files changed, 2867 insertions(+) create mode 100644 jextract/.gitignore create mode 100644 jextract/README.md create mode 100644 jextract/bench/src/bench/SqliteBench.java create mode 100644 jextract/generated/demo/sqlite/sqlite3_bind_text$x0.java create mode 100644 jextract/generated/demo/sqlite/sqlite3_exec$callback.java create mode 100644 jextract/generated/demo/sqlite/sqlite3_h$shared.java create mode 100644 jextract/generated/demo/sqlite/sqlite3_h.java create mode 100644 jextract/jni/SqliteJni.java create mode 100644 jextract/jni/sqlite_jni.c create mode 100644 jextract/output/01-tools.txt create mode 100644 jextract/output/02-generate.txt create mode 100644 jextract/output/03-hello.txt create mode 100644 jextract/output/04-native-access.txt create mode 100644 jextract/output/05-tour.txt create mode 100644 jextract/output/06-arenas.txt create mode 100644 jextract/output/07-pointer-traps.txt create mode 100644 jextract/output/08-errors.txt create mode 100644 jextract/output/09-callbacks.txt create mode 100644 jextract/output/10-jmh.txt create mode 100644 jextract/output/11-library-loading.txt create mode 100755 jextract/scripts/build-jni.sh create mode 100755 jextract/scripts/fetch-jextract.sh create mode 100755 jextract/scripts/fetch-jmh.sh create mode 100755 jextract/scripts/gen.sh create mode 100755 jextract/scripts/run-all.sh create mode 100755 jextract/scripts/run-jmh.sh create mode 100644 jextract/src/demo/Arenas.java create mode 100644 jextract/src/demo/Callbacks.java create mode 100644 jextract/src/demo/Db.java create mode 100644 jextract/src/demo/Errors.java create mode 100644 jextract/src/demo/Hello.java create mode 100644 jextract/src/demo/NullText.java create mode 100644 jextract/src/demo/SqliteException.java create mode 100644 jextract/src/demo/Tour.java create mode 100644 jextract/src/demo/Trap.java create mode 100644 jextract/src/extra/V.java diff --git a/README.md b/README.md index 2039c7a..7e52a8d 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ These modules keep only sources, a `run.sh` and their transcripts (`output/`). T | [`structured-concurrency-final/`](structured-concurrency-final) | Structured Concurrency Is Final in JDK 28 (JEP 543): From Preview to Production | the thread leak of `ExecutorService` and its fix, every built-in `Joiner`, structure violations, timeouts, a custom joiner, JSON thread dumps, scoped values, `javap` diffs of the API on JDK 25, 26, 27 and 28-ea, the compiler output for six pieces of 25/26-era code, and the preview class-file trap between 27 and 28 | | [`kdf/`](kdf) | Key Derivation Function API in Java (JEP 510): HKDF Explained with Real Examples | `javax.crypto.KDF` final in JDK 25, `HKDF-SHA256/384/512`, extract vs expand, RFC 5869 vectors asserted, a hand-written HKDF and 300 generated cases compared with Python `cryptography` and `openssl kdf`, per-tenant keys with length-prefixed `info`, and the mistakes reproduced (same `info`, ambiguous concatenation, PIN through HKDF vs PBKDF2) | | [`classfile-api/`](classfile-api) | The Java ClassFile API (JEP 484): Read, Generate and Transform Bytecode Without ASM | `java.lang.classfile` final since JDK 24: `parse` and `ClassModel` traversal, `ClassFile.build` with `defineClass` and `defineHiddenClass`, five deliberately broken classes (including swapped exception-table labels that `verify()` misses), timing inserted with `ClassTransform`/`MethodTransform`/`CodeTransform`, the same three jobs in ASM 9.11.1 asserted equal, class-file version limits, a JDK 24/25/26 API diff and a look inside `spring-core` 7.0.10 | +| [`jextract/`](jextract) | Calling C from Java with jextract and the FFM API: A Real Library, No JNI | jextract (early-access build for JDK 25) bindings for SQLite 3.45.1, the four arena kinds used wrongly on purpose, `C_POINTER` vs `ADDRESS` (zero-length pointers, and a `SIGSEGV` from `getString` on a NULL pointer), `sqlite3_open` out-parameter, return codes to exceptions, a `sqlite3_exec` upcall (and the JVM exit when it throws), the JDK 25 native-access warning and `--illegal-native-access=deny`, and a JMH comparison of FFM, hand-written JNI and plain Java | ## Captured output (`docs/output/`) diff --git a/jextract/.gitignore b/jextract/.gitignore new file mode 100644 index 0000000..7106a56 --- /dev/null +++ b/jextract/.gitignore @@ -0,0 +1,4 @@ +tools/ +build/ +bench/lib/ +hs_err_pid*.log diff --git a/jextract/README.md b/jextract/README.md new file mode 100644 index 0000000..be098b8 --- /dev/null +++ b/jextract/README.md @@ -0,0 +1,54 @@ +# jextract: calling SQLite from Java with the FFM API + +Companion code for the ankurm.com post **"Calling C from Java with jextract and the FFM API: A Real Library, No JNI"**. No `docs/` folder: the explanation lives in the article. + +| Thing | Version | +|---|---| +| JDK | Temurin 25.0.5+8 (FFM API final since JDK 22, JEP 454) | +| jextract | `openjdk-25-jextract+2-4` early-access build, 2025-11-25 (jdk.java.net/jextract), libclang 13.0.0 | +| SQLite | 3.45.1 (Ubuntu `libsqlite3-dev 3.45.1-1ubuntu2.8`) | +| JMH | 1.37 | +| gcc | 13.3.0 (only for the JNI comparison) | + +## Quickstart + +```bash +sudo apt-get install libsqlite3-dev gcc # header + libsqlite3.so; gcc only for the JNI benchmark +export JAVA_HOME=/path/to/jdk-25 +./scripts/fetch-jextract.sh # downloads and sha256-checks the jextract build into ./tools +export JEXTRACT=$PWD/tools/jextract-25/bin/jextract +./scripts/gen.sh # regenerate generated/ (the committed bindings) +./scripts/run-all.sh # compile everything, regenerate output/01..09 and 11 +./scripts/fetch-jmh.sh && ./scripts/run-jmh.sh # the FFM vs JNI vs plain Java benchmark, about 4 minutes -> output/10-jmh.txt +``` + +Run any demo by hand: `java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Tour`. + +## Layout + +| Path | What | +|---|---| +| `scripts/gen.sh` | the jextract command, filtered to 25 functions and 13 constants | +| `generated/demo/sqlite/` | what jextract produced from that command (4 files, committed so you can read them) | +| `src/demo/` | hand-written code: `Db` (the wrapper), `Hello`, `Tour`, `Arenas`, `Errors`, `Trap`, `NullText`, `Callbacks` | +| `src/extra/V.java` | one-line program used by `run-all.sh` to test library loading with throwaway bindings | +| `jni/` | the JNI twin used by the benchmark: `SqliteJni.java` and `sqlite_jni.c` | +| `bench/src/bench/SqliteBench.java` | JMH benchmark: plain Java vs FFM vs JNI | + +## Captured output (`output/`) + +| File | What it is | +|---|---| +| `01-tools.txt` | versions of every tool used | +| `02-generate.txt` | the trimmed generation vs the whole header: file counts, line counts | +| `03-hello.txt` | first call, plus `-Djextract.trace.downcalls=true` | +| `04-native-access.txt` | JDK 25 with no `--enable-native-access` flag: the warning, and `--illegal-native-access=deny` | +| `05-tour.txt` | open, create, bind, insert, query | +| `06-arenas.txt` | confined, shared, auto and global arenas used wrongly | +| `07-pointer-traps.txt` | zero-length pointers, and the `SIGSEGV` from `getString` on a NULL pointer | +| `08-errors.txt` | return codes turned into exceptions | +| `09-callbacks.txt` | `sqlite3_exec` with an upcall, and an exception thrown inside it | +| `10-jmh.txt` | JMH numbers (machine dependent: shape, not result) | +| `11-library-loading.txt` | what `-l` produces, and the failure when the library name is wrong | + +Timings and addresses change between runs; everything else is byte-stable. The Linux/x64 jextract build is used throughout; nothing here was run on macOS or Windows. diff --git a/jextract/bench/src/bench/SqliteBench.java b/jextract/bench/src/bench/SqliteBench.java new file mode 100644 index 0000000..e5410ec --- /dev/null +++ b/jextract/bench/src/bench/SqliteBench.java @@ -0,0 +1,98 @@ +package bench; + +import static demo.sqlite.sqlite3_h.*; + +import demo.Db; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.*; + +/** + * The same SQLite calls three ways: plain Java (floor), jextract/FFM, hand-written JNI. + * versionNumber: no arguments, as cheap as a native call gets. + * strlike: two C strings in, an int out. + * insert1000: 1000 prepared-statement inserts into an in-memory table, rolled back each time. + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +public class SqliteBench { + static final String GLOB = "ab%d_f"; + static final String TEXT = "abcdef"; + static final String INSERT = "INSERT INTO t(a, b) VALUES (?1, ?2)"; + + int constant = 3045001; // a field, so the JIT cannot fold the "plain Java" call away + MemorySegment cGlob, cText; // allocated once, for the "pre-allocated" FFM variant + Arena global = Arena.ofShared(); + + Db ffmDb; + long jniDb; + String[] rowText = new String[1000]; + + @Setup + public void setup() { + cGlob = global.allocateFrom(GLOB); + cText = global.allocateFrom(TEXT); + for (int i = 0; i < rowText.length; i++) rowText[i] = "row-" + i; + ffmDb = Db.open(":memory:"); + ffmDb.exec("CREATE TABLE t(a INTEGER, b TEXT)"); + jniDb = SqliteJni.open(":memory:"); + SqliteJni.exec(jniDb, "CREATE TABLE t(a INTEGER, b TEXT)"); + // sanity: all three agree before we time anything + if (sqlite3_libversion_number() != SqliteJni.versionNumber()) throw new AssertionError("version mismatch"); + if (sqlite3_strlike(cGlob, cText, 0) != SqliteJni.strlike(GLOB, TEXT)) throw new AssertionError("strlike mismatch"); + } + + @TearDown + public void tearDown() { + ffmDb.close(); + SqliteJni.close(jniDb); + global.close(); + } + + // ---- no-argument call ---- + @Benchmark public int versionNumber_plainJava() { return constant; } + @Benchmark public int versionNumber_ffm() { return sqlite3_libversion_number(); } + @Benchmark public int versionNumber_jni() { return SqliteJni.versionNumber(); } + + // ---- two C strings ---- + @Benchmark public int strlike_ffm_preallocated() { return sqlite3_strlike(cGlob, cText, 0); } + + @Benchmark public int strlike_ffm_arenaPerCall() { + try (Arena a = Arena.ofConfined()) { + return sqlite3_strlike(a.allocateFrom(GLOB), a.allocateFrom(TEXT), 0); + } + } + + @Benchmark public int strlike_jni() { return SqliteJni.strlike(GLOB, TEXT); } + + // ---- a real workload ---- + @Benchmark public int insert1000_ffm() { + ffmDb.exec("BEGIN"); + try (Arena arena = Arena.ofConfined(); Db.Stmt st = ffmDb.prepare(arena, INSERT)) { + for (int i = 0; i < 1000; i++) { + st.bindInt(1, i); + st.bindText(arena, 2, rowText[i]); + st.step(); + st.reset(); + } + } + ffmDb.exec("ROLLBACK"); + return 1000; + } + + @Benchmark public int insert1000_jni() { + SqliteJni.exec(jniDb, "BEGIN"); + long st = SqliteJni.prepare(jniDb, INSERT); + for (int i = 0; i < 1000; i++) { + SqliteJni.bindInt(st, 1, i); + SqliteJni.bindText(st, 2, rowText[i]); + SqliteJni.step(st); + SqliteJni.reset(st); + } + SqliteJni.finalizeStmt(st); + SqliteJni.exec(jniDb, "ROLLBACK"); + return 1000; + } +} diff --git a/jextract/generated/demo/sqlite/sqlite3_bind_text$x0.java b/jextract/generated/demo/sqlite/sqlite3_bind_text$x0.java new file mode 100644 index 0000000..fcad643 --- /dev/null +++ b/jextract/generated/demo/sqlite/sqlite3_bind_text$x0.java @@ -0,0 +1,69 @@ +// Generated by jextract + +package demo.sqlite; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * void (*)(void *) + * } + */ +public final class sqlite3_bind_text$x0 { + + private sqlite3_bind_text$x0() { + // Should not be called directly + } + + /** + * The function pointer signature, expressed as a functional interface + */ + public interface Function { + void apply(MemorySegment _x0); + } + + private static final FunctionDescriptor $DESC = FunctionDescriptor.ofVoid( + sqlite3_h.C_POINTER + ); + + /** + * The descriptor of this function pointer + */ + public static FunctionDescriptor descriptor() { + return $DESC; + } + + private static final MethodHandle UP$MH = sqlite3_h.upcallHandle(sqlite3_bind_text$x0.Function.class, "apply", $DESC); + + /** + * Allocates a new upcall stub, whose implementation is defined by {@code fi}. + * The lifetime of the returned segment is managed by {@code arena} + */ + public static MemorySegment allocate(sqlite3_bind_text$x0.Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(UP$MH.bindTo(fi), $DESC, arena); + } + + private static final MethodHandle DOWN$MH = Linker.nativeLinker().downcallHandle($DESC); + + /** + * Invoke the upcall stub {@code funcPtr}, with given parameters + */ + public static void invoke(MemorySegment funcPtr, MemorySegment _x0) { + try { + DOWN$MH.invokeExact(funcPtr, _x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } +} + diff --git a/jextract/generated/demo/sqlite/sqlite3_exec$callback.java b/jextract/generated/demo/sqlite/sqlite3_exec$callback.java new file mode 100644 index 0000000..167aa81 --- /dev/null +++ b/jextract/generated/demo/sqlite/sqlite3_exec$callback.java @@ -0,0 +1,73 @@ +// Generated by jextract + +package demo.sqlite; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +/** + * {@snippet lang=c : + * int (*callback)(void *, int, char **, char **) + * } + */ +public final class sqlite3_exec$callback { + + private sqlite3_exec$callback() { + // Should not be called directly + } + + /** + * The function pointer signature, expressed as a functional interface + */ + public interface Function { + int apply(MemorySegment _x0, int _x1, MemorySegment _x2, MemorySegment _x3); + } + + private static final FunctionDescriptor $DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER + ); + + /** + * The descriptor of this function pointer + */ + public static FunctionDescriptor descriptor() { + return $DESC; + } + + private static final MethodHandle UP$MH = sqlite3_h.upcallHandle(sqlite3_exec$callback.Function.class, "apply", $DESC); + + /** + * Allocates a new upcall stub, whose implementation is defined by {@code fi}. + * The lifetime of the returned segment is managed by {@code arena} + */ + public static MemorySegment allocate(sqlite3_exec$callback.Function fi, Arena arena) { + return Linker.nativeLinker().upcallStub(UP$MH.bindTo(fi), $DESC, arena); + } + + private static final MethodHandle DOWN$MH = Linker.nativeLinker().downcallHandle($DESC); + + /** + * Invoke the upcall stub {@code funcPtr}, with given parameters + */ + public static int invoke(MemorySegment funcPtr, MemorySegment _x0, int _x1, MemorySegment _x2, MemorySegment _x3) { + try { + return (int) DOWN$MH.invokeExact(funcPtr, _x0, _x1, _x2, _x3); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } +} + diff --git a/jextract/generated/demo/sqlite/sqlite3_h$shared.java b/jextract/generated/demo/sqlite/sqlite3_h$shared.java new file mode 100644 index 0000000..529311b --- /dev/null +++ b/jextract/generated/demo/sqlite/sqlite3_h$shared.java @@ -0,0 +1,63 @@ +// Generated by jextract + +package demo.sqlite; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +public class sqlite3_h$shared { + + sqlite3_h$shared() { + // Should not be called directly + } + + public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) Linker.nativeLinker().canonicalLayouts().get("bool"); + public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char"); + public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short"); + public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int"); + public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long"); + public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float"); + public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double"); + public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*")) + .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); + public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long"); + + static final boolean TRACE_DOWNCALLS = Boolean.getBoolean("jextract.trace.downcalls"); + + static void traceDowncall(String name, Object... args) { + String traceArgs = Arrays.stream(args) + .map(Object::toString) + .collect(Collectors.joining(", ")); + System.out.printf("%s(%s)\n", name, traceArgs); + } + + static MethodHandle upcallHandle(Class fi, String name, FunctionDescriptor fdesc) { + try { + return MethodHandles.lookup().findVirtual(fi, name, fdesc.toMethodType()); + } catch (ReflectiveOperationException ex) { + throw new AssertionError(ex); + } + } + + static MemoryLayout align(MemoryLayout layout, long align) { + return switch (layout) { + case PaddingLayout p -> p; + case ValueLayout v -> v.withByteAlignment(align); + case GroupLayout g -> { + MemoryLayout[] alignedMembers = g.memberLayouts().stream() + .map(m -> align(m, align)).toArray(MemoryLayout[]::new); + yield g instanceof StructLayout ? + MemoryLayout.structLayout(alignedMembers) : MemoryLayout.unionLayout(alignedMembers); + } + case SequenceLayout s -> MemoryLayout.sequenceLayout(s.elementCount(), align(s.elementLayout(), align)); + }; + } +} + diff --git a/jextract/generated/demo/sqlite/sqlite3_h.java b/jextract/generated/demo/sqlite/sqlite3_h.java new file mode 100644 index 0000000..8d0e088 --- /dev/null +++ b/jextract/generated/demo/sqlite/sqlite3_h.java @@ -0,0 +1,1662 @@ +// Generated by jextract + +package demo.sqlite; + +import java.lang.invoke.*; +import java.lang.foreign.*; +import java.nio.ByteOrder; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.MemoryLayout.PathElement.*; + +public class sqlite3_h extends sqlite3_h$shared { + + sqlite3_h() { + // Should not be called directly + } + + static final Arena LIBRARY_ARENA = Arena.ofAuto(); + + static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("sqlite3"), LIBRARY_ARENA) + .or(SymbolLookup.loaderLookup()) + .or(Linker.nativeLinker().defaultLookup()); + + private static final int SQLITE_OK = (int)0L; + /** + * {@snippet lang=c : + * #define SQLITE_OK 0 + * } + */ + public static int SQLITE_OK() { + return SQLITE_OK; + } + private static final int SQLITE_ERROR = (int)1L; + /** + * {@snippet lang=c : + * #define SQLITE_ERROR 1 + * } + */ + public static int SQLITE_ERROR() { + return SQLITE_ERROR; + } + private static final int SQLITE_BUSY = (int)5L; + /** + * {@snippet lang=c : + * #define SQLITE_BUSY 5 + * } + */ + public static int SQLITE_BUSY() { + return SQLITE_BUSY; + } + private static final int SQLITE_CANTOPEN = (int)14L; + /** + * {@snippet lang=c : + * #define SQLITE_CANTOPEN 14 + * } + */ + public static int SQLITE_CANTOPEN() { + return SQLITE_CANTOPEN; + } + private static final int SQLITE_CONSTRAINT = (int)19L; + /** + * {@snippet lang=c : + * #define SQLITE_CONSTRAINT 19 + * } + */ + public static int SQLITE_CONSTRAINT() { + return SQLITE_CONSTRAINT; + } + private static final int SQLITE_ROW = (int)100L; + /** + * {@snippet lang=c : + * #define SQLITE_ROW 100 + * } + */ + public static int SQLITE_ROW() { + return SQLITE_ROW; + } + private static final int SQLITE_DONE = (int)101L; + /** + * {@snippet lang=c : + * #define SQLITE_DONE 101 + * } + */ + public static int SQLITE_DONE() { + return SQLITE_DONE; + } + private static final int SQLITE_OPEN_READONLY = (int)1L; + /** + * {@snippet lang=c : + * #define SQLITE_OPEN_READONLY 1 + * } + */ + public static int SQLITE_OPEN_READONLY() { + return SQLITE_OPEN_READONLY; + } + private static final int SQLITE_OPEN_READWRITE = (int)2L; + /** + * {@snippet lang=c : + * #define SQLITE_OPEN_READWRITE 2 + * } + */ + public static int SQLITE_OPEN_READWRITE() { + return SQLITE_OPEN_READWRITE; + } + private static final int SQLITE_OPEN_CREATE = (int)4L; + /** + * {@snippet lang=c : + * #define SQLITE_OPEN_CREATE 4 + * } + */ + public static int SQLITE_OPEN_CREATE() { + return SQLITE_OPEN_CREATE; + } + private static final int SQLITE_NULL = (int)5L; + /** + * {@snippet lang=c : + * #define SQLITE_NULL 5 + * } + */ + public static int SQLITE_NULL() { + return SQLITE_NULL; + } + + private static class sqlite3_libversion { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_POINTER ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_libversion"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * const char *sqlite3_libversion() + * } + */ + public static FunctionDescriptor sqlite3_libversion$descriptor() { + return sqlite3_libversion.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * const char *sqlite3_libversion() + * } + */ + public static MethodHandle sqlite3_libversion$handle() { + return sqlite3_libversion.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * const char *sqlite3_libversion() + * } + */ + public static MemorySegment sqlite3_libversion$address() { + return sqlite3_libversion.ADDR; + } + + /** + * {@snippet lang=c : + * const char *sqlite3_libversion() + * } + */ + public static MemorySegment sqlite3_libversion() { + var mh$ = sqlite3_libversion.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_libversion"); + } + return (MemorySegment)mh$.invokeExact(); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_sourceid { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_POINTER ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_sourceid"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * const char *sqlite3_sourceid() + * } + */ + public static FunctionDescriptor sqlite3_sourceid$descriptor() { + return sqlite3_sourceid.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * const char *sqlite3_sourceid() + * } + */ + public static MethodHandle sqlite3_sourceid$handle() { + return sqlite3_sourceid.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * const char *sqlite3_sourceid() + * } + */ + public static MemorySegment sqlite3_sourceid$address() { + return sqlite3_sourceid.ADDR; + } + + /** + * {@snippet lang=c : + * const char *sqlite3_sourceid() + * } + */ + public static MemorySegment sqlite3_sourceid() { + var mh$ = sqlite3_sourceid.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_sourceid"); + } + return (MemorySegment)mh$.invokeExact(); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_libversion_number { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_libversion_number"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_libversion_number() + * } + */ + public static FunctionDescriptor sqlite3_libversion_number$descriptor() { + return sqlite3_libversion_number.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_libversion_number() + * } + */ + public static MethodHandle sqlite3_libversion_number$handle() { + return sqlite3_libversion_number.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_libversion_number() + * } + */ + public static MemorySegment sqlite3_libversion_number$address() { + return sqlite3_libversion_number.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_libversion_number() + * } + */ + public static int sqlite3_libversion_number() { + var mh$ = sqlite3_libversion_number.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_libversion_number"); + } + return (int)mh$.invokeExact(); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_close { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_close"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_close(sqlite3 *) + * } + */ + public static FunctionDescriptor sqlite3_close$descriptor() { + return sqlite3_close.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_close(sqlite3 *) + * } + */ + public static MethodHandle sqlite3_close$handle() { + return sqlite3_close.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_close(sqlite3 *) + * } + */ + public static MemorySegment sqlite3_close$address() { + return sqlite3_close.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_close(sqlite3 *) + * } + */ + public static int sqlite3_close(MemorySegment x0) { + var mh$ = sqlite3_close.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_close", x0); + } + return (int)mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_exec { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_exec"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_exec(sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, char **errmsg) + * } + */ + public static FunctionDescriptor sqlite3_exec$descriptor() { + return sqlite3_exec.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_exec(sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, char **errmsg) + * } + */ + public static MethodHandle sqlite3_exec$handle() { + return sqlite3_exec.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_exec(sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, char **errmsg) + * } + */ + public static MemorySegment sqlite3_exec$address() { + return sqlite3_exec.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_exec(sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, char **errmsg) + * } + */ + public static int sqlite3_exec(MemorySegment x0, MemorySegment sql, MemorySegment callback, MemorySegment x3, MemorySegment errmsg) { + var mh$ = sqlite3_exec.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_exec", x0, sql, callback, x3, errmsg); + } + return (int)mh$.invokeExact(x0, sql, callback, x3, errmsg); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_last_insert_rowid { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_LONG_LONG, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_last_insert_rowid"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * sqlite3_int64 sqlite3_last_insert_rowid(sqlite3 *) + * } + */ + public static FunctionDescriptor sqlite3_last_insert_rowid$descriptor() { + return sqlite3_last_insert_rowid.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * sqlite3_int64 sqlite3_last_insert_rowid(sqlite3 *) + * } + */ + public static MethodHandle sqlite3_last_insert_rowid$handle() { + return sqlite3_last_insert_rowid.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * sqlite3_int64 sqlite3_last_insert_rowid(sqlite3 *) + * } + */ + public static MemorySegment sqlite3_last_insert_rowid$address() { + return sqlite3_last_insert_rowid.ADDR; + } + + /** + * {@snippet lang=c : + * sqlite3_int64 sqlite3_last_insert_rowid(sqlite3 *) + * } + */ + public static long sqlite3_last_insert_rowid(MemorySegment x0) { + var mh$ = sqlite3_last_insert_rowid.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_last_insert_rowid", x0); + } + return (long)mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_changes { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_changes"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_changes(sqlite3 *) + * } + */ + public static FunctionDescriptor sqlite3_changes$descriptor() { + return sqlite3_changes.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_changes(sqlite3 *) + * } + */ + public static MethodHandle sqlite3_changes$handle() { + return sqlite3_changes.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_changes(sqlite3 *) + * } + */ + public static MemorySegment sqlite3_changes$address() { + return sqlite3_changes.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_changes(sqlite3 *) + * } + */ + public static int sqlite3_changes(MemorySegment x0) { + var mh$ = sqlite3_changes.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_changes", x0); + } + return (int)mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_free { + public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_free"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * void sqlite3_free(void *) + * } + */ + public static FunctionDescriptor sqlite3_free$descriptor() { + return sqlite3_free.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * void sqlite3_free(void *) + * } + */ + public static MethodHandle sqlite3_free$handle() { + return sqlite3_free.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * void sqlite3_free(void *) + * } + */ + public static MemorySegment sqlite3_free$address() { + return sqlite3_free.ADDR; + } + + /** + * {@snippet lang=c : + * void sqlite3_free(void *) + * } + */ + public static void sqlite3_free(MemorySegment x0) { + var mh$ = sqlite3_free.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_free", x0); + } + mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_open { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_open"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_open(const char *filename, sqlite3 **ppDb) + * } + */ + public static FunctionDescriptor sqlite3_open$descriptor() { + return sqlite3_open.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_open(const char *filename, sqlite3 **ppDb) + * } + */ + public static MethodHandle sqlite3_open$handle() { + return sqlite3_open.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_open(const char *filename, sqlite3 **ppDb) + * } + */ + public static MemorySegment sqlite3_open$address() { + return sqlite3_open.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_open(const char *filename, sqlite3 **ppDb) + * } + */ + public static int sqlite3_open(MemorySegment filename, MemorySegment ppDb) { + var mh$ = sqlite3_open.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_open", filename, ppDb); + } + return (int)mh$.invokeExact(filename, ppDb); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_open_v2 { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_open_v2"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) + * } + */ + public static FunctionDescriptor sqlite3_open_v2$descriptor() { + return sqlite3_open_v2.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) + * } + */ + public static MethodHandle sqlite3_open_v2$handle() { + return sqlite3_open_v2.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) + * } + */ + public static MemorySegment sqlite3_open_v2$address() { + return sqlite3_open_v2.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs) + * } + */ + public static int sqlite3_open_v2(MemorySegment filename, MemorySegment ppDb, int flags, MemorySegment zVfs) { + var mh$ = sqlite3_open_v2.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_open_v2", filename, ppDb, flags, zVfs); + } + return (int)mh$.invokeExact(filename, ppDb, flags, zVfs); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_extended_errcode { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_extended_errcode"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_extended_errcode(sqlite3 *db) + * } + */ + public static FunctionDescriptor sqlite3_extended_errcode$descriptor() { + return sqlite3_extended_errcode.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_extended_errcode(sqlite3 *db) + * } + */ + public static MethodHandle sqlite3_extended_errcode$handle() { + return sqlite3_extended_errcode.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_extended_errcode(sqlite3 *db) + * } + */ + public static MemorySegment sqlite3_extended_errcode$address() { + return sqlite3_extended_errcode.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_extended_errcode(sqlite3 *db) + * } + */ + public static int sqlite3_extended_errcode(MemorySegment db) { + var mh$ = sqlite3_extended_errcode.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_extended_errcode", db); + } + return (int)mh$.invokeExact(db); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_errmsg { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_errmsg"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * const char *sqlite3_errmsg(sqlite3 *) + * } + */ + public static FunctionDescriptor sqlite3_errmsg$descriptor() { + return sqlite3_errmsg.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * const char *sqlite3_errmsg(sqlite3 *) + * } + */ + public static MethodHandle sqlite3_errmsg$handle() { + return sqlite3_errmsg.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * const char *sqlite3_errmsg(sqlite3 *) + * } + */ + public static MemorySegment sqlite3_errmsg$address() { + return sqlite3_errmsg.ADDR; + } + + /** + * {@snippet lang=c : + * const char *sqlite3_errmsg(sqlite3 *) + * } + */ + public static MemorySegment sqlite3_errmsg(MemorySegment x0) { + var mh$ = sqlite3_errmsg.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_errmsg", x0); + } + return (MemorySegment)mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_errstr { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_POINTER, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_errstr"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * const char *sqlite3_errstr(int) + * } + */ + public static FunctionDescriptor sqlite3_errstr$descriptor() { + return sqlite3_errstr.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * const char *sqlite3_errstr(int) + * } + */ + public static MethodHandle sqlite3_errstr$handle() { + return sqlite3_errstr.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * const char *sqlite3_errstr(int) + * } + */ + public static MemorySegment sqlite3_errstr$address() { + return sqlite3_errstr.ADDR; + } + + /** + * {@snippet lang=c : + * const char *sqlite3_errstr(int) + * } + */ + public static MemorySegment sqlite3_errstr(int x0) { + var mh$ = sqlite3_errstr.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_errstr", x0); + } + return (MemorySegment)mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_prepare_v2 { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_prepare_v2"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) + * } + */ + public static FunctionDescriptor sqlite3_prepare_v2$descriptor() { + return sqlite3_prepare_v2.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) + * } + */ + public static MethodHandle sqlite3_prepare_v2$handle() { + return sqlite3_prepare_v2.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) + * } + */ + public static MemorySegment sqlite3_prepare_v2$address() { + return sqlite3_prepare_v2.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail) + * } + */ + public static int sqlite3_prepare_v2(MemorySegment db, MemorySegment zSql, int nByte, MemorySegment ppStmt, MemorySegment pzTail) { + var mh$ = sqlite3_prepare_v2.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_prepare_v2", db, zSql, nByte, ppStmt, pzTail); + } + return (int)mh$.invokeExact(db, zSql, nByte, ppStmt, pzTail); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_bind_int { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_bind_int"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_bind_int(sqlite3_stmt *, int, int) + * } + */ + public static FunctionDescriptor sqlite3_bind_int$descriptor() { + return sqlite3_bind_int.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_bind_int(sqlite3_stmt *, int, int) + * } + */ + public static MethodHandle sqlite3_bind_int$handle() { + return sqlite3_bind_int.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_bind_int(sqlite3_stmt *, int, int) + * } + */ + public static MemorySegment sqlite3_bind_int$address() { + return sqlite3_bind_int.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_bind_int(sqlite3_stmt *, int, int) + * } + */ + public static int sqlite3_bind_int(MemorySegment x0, int x1, int x2) { + var mh$ = sqlite3_bind_int.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_bind_int", x0, x1, x2); + } + return (int)mh$.invokeExact(x0, x1, x2); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_bind_text { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_bind_text"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_bind_text(sqlite3_stmt *, int, const char *, int, void (*)(void *)) + * } + */ + public static FunctionDescriptor sqlite3_bind_text$descriptor() { + return sqlite3_bind_text.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_bind_text(sqlite3_stmt *, int, const char *, int, void (*)(void *)) + * } + */ + public static MethodHandle sqlite3_bind_text$handle() { + return sqlite3_bind_text.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_bind_text(sqlite3_stmt *, int, const char *, int, void (*)(void *)) + * } + */ + public static MemorySegment sqlite3_bind_text$address() { + return sqlite3_bind_text.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_bind_text(sqlite3_stmt *, int, const char *, int, void (*)(void *)) + * } + */ + public static int sqlite3_bind_text(MemorySegment x0, int x1, MemorySegment x2, int x3, MemorySegment x4) { + var mh$ = sqlite3_bind_text.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_bind_text", x0, x1, x2, x3, x4); + } + return (int)mh$.invokeExact(x0, x1, x2, x3, x4); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_column_count { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_column_count"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_column_count(sqlite3_stmt *pStmt) + * } + */ + public static FunctionDescriptor sqlite3_column_count$descriptor() { + return sqlite3_column_count.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_column_count(sqlite3_stmt *pStmt) + * } + */ + public static MethodHandle sqlite3_column_count$handle() { + return sqlite3_column_count.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_column_count(sqlite3_stmt *pStmt) + * } + */ + public static MemorySegment sqlite3_column_count$address() { + return sqlite3_column_count.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_column_count(sqlite3_stmt *pStmt) + * } + */ + public static int sqlite3_column_count(MemorySegment pStmt) { + var mh$ = sqlite3_column_count.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_column_count", pStmt); + } + return (int)mh$.invokeExact(pStmt); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_column_name { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_column_name"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * const char *sqlite3_column_name(sqlite3_stmt *, int N) + * } + */ + public static FunctionDescriptor sqlite3_column_name$descriptor() { + return sqlite3_column_name.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * const char *sqlite3_column_name(sqlite3_stmt *, int N) + * } + */ + public static MethodHandle sqlite3_column_name$handle() { + return sqlite3_column_name.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * const char *sqlite3_column_name(sqlite3_stmt *, int N) + * } + */ + public static MemorySegment sqlite3_column_name$address() { + return sqlite3_column_name.ADDR; + } + + /** + * {@snippet lang=c : + * const char *sqlite3_column_name(sqlite3_stmt *, int N) + * } + */ + public static MemorySegment sqlite3_column_name(MemorySegment x0, int N) { + var mh$ = sqlite3_column_name.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_column_name", x0, N); + } + return (MemorySegment)mh$.invokeExact(x0, N); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_step { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_step"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_step(sqlite3_stmt *) + * } + */ + public static FunctionDescriptor sqlite3_step$descriptor() { + return sqlite3_step.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_step(sqlite3_stmt *) + * } + */ + public static MethodHandle sqlite3_step$handle() { + return sqlite3_step.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_step(sqlite3_stmt *) + * } + */ + public static MemorySegment sqlite3_step$address() { + return sqlite3_step.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_step(sqlite3_stmt *) + * } + */ + public static int sqlite3_step(MemorySegment x0) { + var mh$ = sqlite3_step.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_step", x0); + } + return (int)mh$.invokeExact(x0); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_column_int { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_column_int"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_column_int(sqlite3_stmt *, int iCol) + * } + */ + public static FunctionDescriptor sqlite3_column_int$descriptor() { + return sqlite3_column_int.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_column_int(sqlite3_stmt *, int iCol) + * } + */ + public static MethodHandle sqlite3_column_int$handle() { + return sqlite3_column_int.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_column_int(sqlite3_stmt *, int iCol) + * } + */ + public static MemorySegment sqlite3_column_int$address() { + return sqlite3_column_int.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_column_int(sqlite3_stmt *, int iCol) + * } + */ + public static int sqlite3_column_int(MemorySegment x0, int iCol) { + var mh$ = sqlite3_column_int.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_column_int", x0, iCol); + } + return (int)mh$.invokeExact(x0, iCol); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_column_text { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_column_text"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * const unsigned char *sqlite3_column_text(sqlite3_stmt *, int iCol) + * } + */ + public static FunctionDescriptor sqlite3_column_text$descriptor() { + return sqlite3_column_text.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * const unsigned char *sqlite3_column_text(sqlite3_stmt *, int iCol) + * } + */ + public static MethodHandle sqlite3_column_text$handle() { + return sqlite3_column_text.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * const unsigned char *sqlite3_column_text(sqlite3_stmt *, int iCol) + * } + */ + public static MemorySegment sqlite3_column_text$address() { + return sqlite3_column_text.ADDR; + } + + /** + * {@snippet lang=c : + * const unsigned char *sqlite3_column_text(sqlite3_stmt *, int iCol) + * } + */ + public static MemorySegment sqlite3_column_text(MemorySegment x0, int iCol) { + var mh$ = sqlite3_column_text.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_column_text", x0, iCol); + } + return (MemorySegment)mh$.invokeExact(x0, iCol); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_column_type { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_column_type"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_column_type(sqlite3_stmt *, int iCol) + * } + */ + public static FunctionDescriptor sqlite3_column_type$descriptor() { + return sqlite3_column_type.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_column_type(sqlite3_stmt *, int iCol) + * } + */ + public static MethodHandle sqlite3_column_type$handle() { + return sqlite3_column_type.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_column_type(sqlite3_stmt *, int iCol) + * } + */ + public static MemorySegment sqlite3_column_type$address() { + return sqlite3_column_type.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_column_type(sqlite3_stmt *, int iCol) + * } + */ + public static int sqlite3_column_type(MemorySegment x0, int iCol) { + var mh$ = sqlite3_column_type.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_column_type", x0, iCol); + } + return (int)mh$.invokeExact(x0, iCol); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_finalize { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_finalize"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_finalize(sqlite3_stmt *pStmt) + * } + */ + public static FunctionDescriptor sqlite3_finalize$descriptor() { + return sqlite3_finalize.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_finalize(sqlite3_stmt *pStmt) + * } + */ + public static MethodHandle sqlite3_finalize$handle() { + return sqlite3_finalize.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_finalize(sqlite3_stmt *pStmt) + * } + */ + public static MemorySegment sqlite3_finalize$address() { + return sqlite3_finalize.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_finalize(sqlite3_stmt *pStmt) + * } + */ + public static int sqlite3_finalize(MemorySegment pStmt) { + var mh$ = sqlite3_finalize.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_finalize", pStmt); + } + return (int)mh$.invokeExact(pStmt); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_reset { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_reset"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_reset(sqlite3_stmt *pStmt) + * } + */ + public static FunctionDescriptor sqlite3_reset$descriptor() { + return sqlite3_reset.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_reset(sqlite3_stmt *pStmt) + * } + */ + public static MethodHandle sqlite3_reset$handle() { + return sqlite3_reset.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_reset(sqlite3_stmt *pStmt) + * } + */ + public static MemorySegment sqlite3_reset$address() { + return sqlite3_reset.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_reset(sqlite3_stmt *pStmt) + * } + */ + public static int sqlite3_reset(MemorySegment pStmt) { + var mh$ = sqlite3_reset.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_reset", pStmt); + } + return (int)mh$.invokeExact(pStmt); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static class sqlite3_strlike { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + sqlite3_h.C_INT, + sqlite3_h.C_POINTER, + sqlite3_h.C_POINTER, + sqlite3_h.C_INT + ); + + public static final MemorySegment ADDR = SYMBOL_LOOKUP.findOrThrow("sqlite3_strlike"); + + public static final MethodHandle HANDLE = Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + /** + * Function descriptor for: + * {@snippet lang=c : + * int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc) + * } + */ + public static FunctionDescriptor sqlite3_strlike$descriptor() { + return sqlite3_strlike.DESC; + } + + /** + * Downcall method handle for: + * {@snippet lang=c : + * int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc) + * } + */ + public static MethodHandle sqlite3_strlike$handle() { + return sqlite3_strlike.HANDLE; + } + + /** + * Address for: + * {@snippet lang=c : + * int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc) + * } + */ + public static MemorySegment sqlite3_strlike$address() { + return sqlite3_strlike.ADDR; + } + + /** + * {@snippet lang=c : + * int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc) + * } + */ + public static int sqlite3_strlike(MemorySegment zGlob, MemorySegment zStr, int cEsc) { + var mh$ = sqlite3_strlike.HANDLE; + try { + if (TRACE_DOWNCALLS) { + traceDowncall("sqlite3_strlike", zGlob, zStr, cEsc); + } + return (int)mh$.invokeExact(zGlob, zStr, cEsc); + } catch (Error | RuntimeException ex) { + throw ex; + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + private static final MemorySegment SQLITE_STATIC = MemorySegment.ofAddress(0L); + /** + * {@snippet lang=c : + * #define SQLITE_STATIC (void*) 0 + * } + */ + public static MemorySegment SQLITE_STATIC() { + return SQLITE_STATIC; + } + private static final MemorySegment SQLITE_TRANSIENT = MemorySegment.ofAddress(-1L); + /** + * {@snippet lang=c : + * #define SQLITE_TRANSIENT (void*) -1 + * } + */ + public static MemorySegment SQLITE_TRANSIENT() { + return SQLITE_TRANSIENT; + } +} + diff --git a/jextract/jni/SqliteJni.java b/jextract/jni/SqliteJni.java new file mode 100644 index 0000000..fda5fd6 --- /dev/null +++ b/jextract/jni/SqliteJni.java @@ -0,0 +1,22 @@ +package bench; + +/** The JNI twin of the jextract bindings: the same SQLite calls, through a hand-written C shim (jni/sqlite_jni.c). */ +public final class SqliteJni { + static { + System.loadLibrary("sqlitejni"); // libsqlitejni.so, found through -Djava.library.path + } + + private SqliteJni() {} + + public static native int versionNumber(); + public static native int strlike(String glob, String text); + public static native long open(String path); + public static native int exec(long db, String sql); + public static native long prepare(long db, String sql); + public static native int bindInt(long stmt, int index, int value); + public static native int bindText(long stmt, int index, String value); + public static native int step(long stmt); + public static native int reset(long stmt); + public static native int finalizeStmt(long stmt); + public static native int close(long db); +} diff --git a/jextract/jni/sqlite_jni.c b/jextract/jni/sqlite_jni.c new file mode 100644 index 0000000..1e95a74 --- /dev/null +++ b/jextract/jni/sqlite_jni.c @@ -0,0 +1,69 @@ +/* The hand-written JNI shim: one C function per Java native method, with the marshalling done by hand. + Header generated by `javac -h`; see scripts/build-jni.sh. */ +#include +#include +#include +#include "bench_SqliteJni.h" + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_versionNumber(JNIEnv *env, jclass c) { + return sqlite3_libversion_number(); +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_strlike(JNIEnv *env, jclass c, jstring glob, jstring text) { + const char *g = (*env)->GetStringUTFChars(env, glob, NULL); + const char *t = (*env)->GetStringUTFChars(env, text, NULL); + int r = sqlite3_strlike(g, t, 0); + (*env)->ReleaseStringUTFChars(env, text, t); + (*env)->ReleaseStringUTFChars(env, glob, g); + return r; +} + +JNIEXPORT jlong JNICALL Java_bench_SqliteJni_open(JNIEnv *env, jclass c, jstring path) { + const char *p = (*env)->GetStringUTFChars(env, path, NULL); + sqlite3 *db = NULL; + int rc = sqlite3_open(p, &db); + (*env)->ReleaseStringUTFChars(env, path, p); + return rc == SQLITE_OK ? (jlong)(intptr_t)db : 0; +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_exec(JNIEnv *env, jclass c, jlong db, jstring sql) { + const char *s = (*env)->GetStringUTFChars(env, sql, NULL); + int rc = sqlite3_exec((sqlite3 *)(intptr_t)db, s, NULL, NULL, NULL); + (*env)->ReleaseStringUTFChars(env, sql, s); + return rc; +} + +JNIEXPORT jlong JNICALL Java_bench_SqliteJni_prepare(JNIEnv *env, jclass c, jlong db, jstring sql) { + const char *s = (*env)->GetStringUTFChars(env, sql, NULL); + sqlite3_stmt *st = NULL; + int rc = sqlite3_prepare_v2((sqlite3 *)(intptr_t)db, s, -1, &st, NULL); + (*env)->ReleaseStringUTFChars(env, sql, s); + return rc == SQLITE_OK ? (jlong)(intptr_t)st : 0; +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_bindInt(JNIEnv *env, jclass c, jlong st, jint i, jint v) { + return sqlite3_bind_int((sqlite3_stmt *)(intptr_t)st, i, v); +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_bindText(JNIEnv *env, jclass c, jlong st, jint i, jstring v) { + const char *s = (*env)->GetStringUTFChars(env, v, NULL); + int rc = sqlite3_bind_text((sqlite3_stmt *)(intptr_t)st, i, s, -1, SQLITE_TRANSIENT); + (*env)->ReleaseStringUTFChars(env, v, s); + return rc; +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_step(JNIEnv *env, jclass c, jlong st) { + return sqlite3_step((sqlite3_stmt *)(intptr_t)st); +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_reset(JNIEnv *env, jclass c, jlong st) { + return sqlite3_reset((sqlite3_stmt *)(intptr_t)st); +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_finalizeStmt(JNIEnv *env, jclass c, jlong st) { + return sqlite3_finalize((sqlite3_stmt *)(intptr_t)st); +} + +JNIEXPORT jint JNICALL Java_bench_SqliteJni_close(JNIEnv *env, jclass c, jlong db) { + return sqlite3_close((sqlite3 *)(intptr_t)db); +} diff --git a/jextract/output/01-tools.txt b/jextract/output/01-tools.txt new file mode 100644 index 0000000..bd934de --- /dev/null +++ b/jextract/output/01-tools.txt @@ -0,0 +1,16 @@ +$ jextract --version +jextract 25 +JDK version 25+37-3491 +LibClang version clang version 13.0.0 +$ java -version +openjdk version "25.0.5" 2026-10-20 LTS +OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS) +OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (build 25.0.5+8-LTS, mixed mode, sharing) +$ gcc --version | head -1 +gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0 +$ dpkg-query -W libsqlite3-dev libsqlite3-0 +libsqlite3-0:amd64 3.45.1-1ubuntu2.8 +libsqlite3-dev:amd64 3.45.1-1ubuntu2.8 +$ grep -E '#define SQLITE_(VERSION|VERSION_NUMBER) ' /usr/include/sqlite3.h +#define SQLITE_VERSION "3.45.1" +#define SQLITE_VERSION_NUMBER 3045001 diff --git a/jextract/output/02-generate.txt b/jextract/output/02-generate.txt new file mode 100644 index 0000000..0bb1c30 --- /dev/null +++ b/jextract/output/02-generate.txt @@ -0,0 +1,26 @@ +$ scripts/gen.sh +$ find generated -name '*.java' | wc -l +4 +$ wc -l $(find generated -name '*.java') | sort -n + 63 generated/demo/sqlite/sqlite3_h$shared.java + 69 generated/demo/sqlite/sqlite3_bind_text$x0.java + 73 generated/demo/sqlite/sqlite3_exec$callback.java + 1662 generated/demo/sqlite/sqlite3_h.java + 1867 total +$ jextract --output -t demo.sqlite.full -l sqlite3 /usr/include/sqlite3.h # no filters: the whole header +warnings: 14 +sqlite3.h:272:16: warning: Skipping sqlite3 (type Declared(sqlite3) is not supported) +sqlite3.h:4045:16: warning: Skipping sqlite3_stmt (type Declared(sqlite3_stmt) is not supported) +files: 83 +lines: 44295 +bytes: 1339454 +lines in the one big header class: 22335 +$ grep -o 'public static [A-Za-z.]* sqlite3_open[a-z_0-9$]*(' generated/demo/sqlite/sqlite3_h.java | sort -u +public static FunctionDescriptor sqlite3_open$descriptor( +public static FunctionDescriptor sqlite3_open_v2$descriptor( +public static MemorySegment sqlite3_open$address( +public static MemorySegment sqlite3_open_v2$address( +public static MethodHandle sqlite3_open$handle( +public static MethodHandle sqlite3_open_v2$handle( +public static int sqlite3_open( +public static int sqlite3_open_v2( diff --git a/jextract/output/03-hello.txt b/jextract/output/03-hello.txt new file mode 100644 index 0000000..bbc02b6 --- /dev/null +++ b/jextract/output/03-hello.txt @@ -0,0 +1,14 @@ +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Hello +sqlite3_libversion() = 3.45.1 +returned segment byteSize = 9223372036854775807 +sqlite3_libversion_number() = 3045001 +sqlite3_sourceid() = 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1 + +$ java --enable-native-access=ALL-UNNAMED -Djextract.trace.downcalls=true -cp build/classes demo.Hello +sqlite3_libversion() +sqlite3_libversion() = 3.45.1 +returned segment byteSize = 9223372036854775807 +sqlite3_libversion_number() +sqlite3_libversion_number() = 3045001 +sqlite3_sourceid() +sqlite3_sourceid() = 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1 diff --git a/jextract/output/04-native-access.txt b/jextract/output/04-native-access.txt new file mode 100644 index 0000000..8890649 --- /dev/null +++ b/jextract/output/04-native-access.txt @@ -0,0 +1,18 @@ +$ java -cp build/classes demo.Hello # no flag at all, JDK 25 default +WARNING: A restricted method in java.lang.foreign.AddressLayout has been called +WARNING: java.lang.foreign.AddressLayout::withTargetLayout has been called by demo.sqlite.sqlite3_h$shared in an unnamed module (file:/tmp/work/jextract/repo/jextract/build/classes/) +WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module +WARNING: Restricted methods will be blocked in a future release unless native access is enabled + +sqlite3_libversion() = 3.45.1 +returned segment byteSize = 9223372036854775807 +sqlite3_libversion_number() = 3045001 +sqlite3_sourceid() = 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1 + +$ java --illegal-native-access=deny -cp build/classes demo.Hello +Exception in thread "main" java.lang.ExceptionInInitializerError + at demo.Hello.main(Hello.java:10) +Caused by: java.lang.IllegalCallerException: Illegal native access from an unnamed module (file:/tmp/work/jextract/repo/jextract/build/classes/) + at java.base/java.lang.Module.ensureNativeAccess(Module.java:307) + at java.base/java.lang.System$1.ensureNativeAccess(System.java:2107) + at java.base/jdk.internal.reflect.Reflection.ensureNativeAccess(Reflection.java:123) diff --git a/jextract/output/05-tour.txt b/jextract/output/05-tour.txt new file mode 100644 index 0000000..4e80390 --- /dev/null +++ b/jextract/output/05-tour.txt @@ -0,0 +1,6 @@ +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Tour +rows changed by last statement: 1, last rowid: 3 +[1, Ankur, 41] +[2, Asha, 36] +[3, Ravi, 29] +[[NoAge, null]] diff --git a/jextract/output/06-arenas.txt b/jextract/output/06-arenas.txt new file mode 100644 index 0000000..c203b1c --- /dev/null +++ b/jextract/output/06-arenas.txt @@ -0,0 +1,21 @@ +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Arenas +== confined: one owner thread +owner reads -> ok +other thread reads -> WrongThreadException: Attempted access outside owning thread +other thread closes -> WrongThreadException: Attempted access outside owning thread +read after close -> IllegalStateException: Already closed +close twice -> IllegalStateException: Already closed +== shared: any thread, one close +other thread reads -> ok +other thread closes -> ok +read after close -> IllegalStateException: Already closed +== auto: the garbage collector frees it; close() is not allowed +allocate 8 bytes -> ok +close() -> UnsupportedOperationException: Attempted to close a non-closeable session +== global: never freed; close() is not allowed +allocate 8 bytes -> ok +close() -> UnsupportedOperationException: Attempted to close a non-closeable session +== bounds: a segment knows its size +read int at offset 0 -> ok +read int at offset 4 -> IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x7fba74666600, byteSize: 4 }; new offset = 4; new length = 4 +read long at offset 0 -> IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x7fba74666600, byteSize: 4 }; new offset = 0; new length = 8 diff --git a/jextract/output/07-pointer-traps.txt b/jextract/output/07-pointer-traps.txt new file mode 100644 index 0000000..ed7bb90 --- /dev/null +++ b/jextract/output/07-pointer-traps.txt @@ -0,0 +1,18 @@ +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Trap +ValueLayout.ADDRESS : byteSize=0 +getString(0) : IndexOutOfBoundsException, "No null terminator found..." +reinterpret(100) : hello from C land +C_POINTER : byteSize=9223372036854775807 +getString(0) : hello from C land + +$ java --enable-native-access=ALL-UNNAMED -XX:ErrorFile=/tmp/jextract-hs_err.log -cp build/classes demo.NullText # first lines of the crash banner +address=0 byteSize=9223372036854775807 equals(NULL)=true +about to call getString(0) on it +# +# A fatal error has been detected by the Java Runtime Environment: +# +# SIGSEGV (0xb) at pc=0x00007f002fe4645d, pid=20843, tid=20844 +# +# JRE version: OpenJDK Runtime Environment Temurin-25.0.5+8 (25.0.5+8) (build 25.0.5+8-LTS) +# Java VM: OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (25.0.5+8-LTS, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64) +exit code: 134 diff --git a/jextract/output/08-errors.txt b/jextract/output/08-errors.txt new file mode 100644 index 0000000..1dadb6f --- /dev/null +++ b/jextract/output/08-errors.txt @@ -0,0 +1,8 @@ +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Errors +open in a directory that does not exist -> unable to open database file (rc=14) +open a missing file read-only -> unable to open database file (rc=14) +syntax error -> near "SELEC": syntax error (rc=1) +missing table -> no such table: ghost (rc=1) +NOT NULL violation -> NOT NULL constraint failed: t.name (rc=19) +PRIMARY KEY violation -> UNIQUE constraint failed: t.id (rc=19) +SQLITE_CONSTRAINT = 19, sqlite3_errstr(19) = constraint failed diff --git a/jextract/output/09-callbacks.txt b/jextract/output/09-callbacks.txt new file mode 100644 index 0000000..73828bf --- /dev/null +++ b/jextract/output/09-callbacks.txt @@ -0,0 +1,13 @@ +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Callbacks +row: id=1 name=one +row: id=2 name=two +row: id=3 name=NULL +sqlite3_exec returned 0 + +$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Callbacks explode +java.lang.IllegalStateException: thrown inside the callback + at demo.Callbacks.lambda$main$0(Callbacks.java:23) + at demo.sqlite.sqlite3_h.sqlite3_exec(sqlite3_h.java:417) + at demo.Callbacks.main(Callbacks.java:39) +Unrecoverable uncaught exception encountered. The VM will now exit +exit code: 1 diff --git a/jextract/output/10-jmh.txt b/jextract/output/10-jmh.txt new file mode 100644 index 0000000..88cbd65 --- /dev/null +++ b/jextract/output/10-jmh.txt @@ -0,0 +1,14 @@ +$ java --enable-native-access=ALL-UNNAMED -Djava.library.path=build/lib org.openjdk.jmh.Main -f 5 -wi 3 -w 1 -i 5 -r 1 (25.0.5+8-LTS, JMH 1.37) +machine: 2 CPUs, Intel(R) Xeon(R) Processor @ 2.10GHz +load average before the run: 4.00 4.80 2.92 (1, 5, 15 minutes; this box has 2 CPUs and was shared with other jobs) +sqlite: libsqlite3-dev 3.45.1-1ubuntu2.8 +Benchmark Mode Cnt Score Error Units +SqliteBench.insert1000_ffm avgt 25 653368.858 ± 161095.825 ns/op +SqliteBench.insert1000_jni avgt 25 544963.964 ± 97090.203 ns/op +SqliteBench.strlike_ffm_arenaPerCall avgt 25 192.233 ± 62.055 ns/op +SqliteBench.strlike_ffm_preallocated avgt 25 82.660 ± 16.253 ns/op +SqliteBench.strlike_jni avgt 25 244.929 ± 82.330 ns/op +SqliteBench.versionNumber_ffm avgt 25 16.819 ± 4.063 ns/op +SqliteBench.versionNumber_jni avgt 25 14.054 ± 4.949 ns/op +SqliteBench.versionNumber_plainJava avgt 25 0.651 ± 0.162 ns/op +load average after the run: 5.97 6.05 4.10 diff --git a/jextract/output/11-library-loading.txt b/jextract/output/11-library-loading.txt new file mode 100644 index 0000000..d05e22e --- /dev/null +++ b/jextract/output/11-library-loading.txt @@ -0,0 +1,14 @@ +$ jextract -t demo.sqlite -l sqlitez --include-function sqlite3_libversion --header-class-name sqlite3_h /usr/include/sqlite3.h +static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("sqlitez"), LIBRARY_ARENA) +$ java --enable-native-access=ALL-UNNAMED -cp V +Exception in thread "main" java.lang.ExceptionInInitializerError + at V.main(V.java:6) +Caused by: java.lang.IllegalArgumentException: Cannot open library: libsqlitez.so + at java.base/java.lang.foreign.SymbolLookup.libraryLookup(SymbolLookup.java:350) + at java.base/java.lang.foreign.SymbolLookup.libraryLookup(SymbolLookup.java:301) + +$ jextract -t demo.sqlite -l :libsqlite3.so.0 --include-function sqlite3_libversion --header-class-name sqlite3_h /usr/include/sqlite3.h +static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup("libsqlite3.so.0", LIBRARY_ARENA) +$ java --enable-native-access=ALL-UNNAMED -cp V +sqlite3_libversion() = 3.45.1 + diff --git a/jextract/scripts/build-jni.sh b/jextract/scripts/build-jni.sh new file mode 100755 index 0000000..5f108dd --- /dev/null +++ b/jextract/scripts/build-jni.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Compile the JNI twin: javac -h generates the header, gcc builds libsqlitejni.so. Output goes to $BUILD (default ./build). +set -euo pipefail +unset JAVA_TOOL_OPTIONS +HERE="$(cd "$(dirname "$0")/.." && pwd)" +JAVA_HOME="${JAVA_HOME:?set JAVA_HOME to a JDK 25}" +BUILD="${BUILD:-$HERE/build}"; mkdir -p "$BUILD/jni-classes" "$BUILD/jni-h" "$BUILD/lib" +"$JAVA_HOME/bin/javac" -h "$BUILD/jni-h" -d "$BUILD/jni-classes" "$HERE/jni/SqliteJni.java" +gcc -O2 -fPIC -shared -Wall -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -I"$BUILD/jni-h" \ + "$HERE/jni/sqlite_jni.c" -o "$BUILD/lib/libsqlitejni.so" -lsqlite3 +echo "built $BUILD/lib/libsqlitejni.so with $(gcc --version | head -1)" diff --git a/jextract/scripts/fetch-jextract.sh b/jextract/scripts/fetch-jextract.sh new file mode 100755 index 0000000..9ca869e --- /dev/null +++ b/jextract/scripts/fetch-jextract.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Downloads the jextract early-access build for JDK 25 (linux-x64) from jdk.java.net and checks its SHA-256. Unpacks into ./tools. +set -euo pipefail +cd "$(dirname "$0")/.."; mkdir -p tools; cd tools +F="openjdk-25-jextract+2-4_linux-x64_bin.tar.gz" +U="https://download.java.net/java/early_access/jextract/25/2/$F" +[ -s "$F" ] || curl -sSLO "$U" +want="$(curl -sSL "$U.sha256" | cut -d' ' -f1)"; got="$(sha256sum "$F" | cut -d' ' -f1)" +[ "$want" = "$got" ] && echo "sha256 ok $got" || { echo "SHA256 MISMATCH"; exit 1; } +tar xzf "$F"; echo "jextract at $(pwd)/jextract-25/bin/jextract" diff --git a/jextract/scripts/fetch-jmh.sh b/jextract/scripts/fetch-jmh.sh new file mode 100755 index 0000000..dcfc6c2 --- /dev/null +++ b/jextract/scripts/fetch-jmh.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Downloads JMH 1.37 and its two runtime dependencies from Maven Central into ./bench/lib, checking each against the published .sha1 +set -euo pipefail +cd "$(dirname "$0")/../bench"; mkdir -p lib +M=https://repo1.maven.org/maven2 +for a in org/openjdk/jmh/jmh-core/1.37/jmh-core-1.37 \ + org/openjdk/jmh/jmh-generator-annprocess/1.37/jmh-generator-annprocess-1.37 \ + net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4 \ + org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1; do + f="lib/$(basename "$a").jar" + for try in 1 2 3 4 5; do [ -s "$f" ] && break; curl -sfL -o "$f" "$M/$a.jar" || { rm -f "$f"; sleep 5; }; done + want=""; for try in 1 2 3 4 5; do want="$(curl -sfL "$M/$a.jar.sha1" | cut -d' ' -f1)" && [ -n "$want" ] && break; sleep 5; done + got="$(sha1sum "$f" | cut -d' ' -f1)" + [ "$want" = "$got" ] && echo "ok $f $got" || { echo "SHA1 MISMATCH $f"; exit 1; } +done diff --git a/jextract/scripts/gen.sh b/jextract/scripts/gen.sh new file mode 100755 index 0000000..8f56086 --- /dev/null +++ b/jextract/scripts/gen.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Regenerate the trimmed SQLite bindings with jextract. Run from anywhere. +# Needs: JEXTRACT (path to the jextract binary), SQLITE_H (default /usr/include/sqlite3.h). +set -euo pipefail +HERE="$(cd "$(dirname "$0")/.." && pwd)" +JEXTRACT="${JEXTRACT:-jextract}" +SQLITE_H="${SQLITE_H:-/usr/include/sqlite3.h}" +OUT="$HERE/generated" +rm -rf "$OUT"; mkdir -p "$OUT" +args=() +for f in sqlite3_libversion sqlite3_libversion_number sqlite3_sourceid \ + sqlite3_open sqlite3_open_v2 sqlite3_close sqlite3_exec \ + sqlite3_errmsg sqlite3_errstr sqlite3_extended_errcode sqlite3_free \ + sqlite3_prepare_v2 sqlite3_step sqlite3_reset sqlite3_finalize \ + sqlite3_bind_int sqlite3_bind_text sqlite3_column_count sqlite3_column_name \ + sqlite3_column_int sqlite3_column_text sqlite3_changes sqlite3_last_insert_rowid sqlite3_strlike sqlite3_column_type; do + args+=(--include-function "$f") +done +for c in SQLITE_OK SQLITE_ERROR SQLITE_BUSY SQLITE_CANTOPEN SQLITE_ROW SQLITE_DONE \ + SQLITE_OPEN_READONLY SQLITE_OPEN_READWRITE SQLITE_OPEN_CREATE SQLITE_TRANSIENT SQLITE_STATIC SQLITE_CONSTRAINT SQLITE_NULL; do + args+=(--include-constant "$c") +done +"$JEXTRACT" --output "$OUT" --target-package demo.sqlite --header-class-name sqlite3_h \ + -l sqlite3 "${args[@]}" "$SQLITE_H" diff --git a/jextract/scripts/run-all.sh b/jextract/scripts/run-all.sh new file mode 100755 index 0000000..db56d9f --- /dev/null +++ b/jextract/scripts/run-all.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Regenerates every file in output/ except 10-jmh.txt (that one has its own script, about 4 minutes). +# JAVA_HOME=/path/to/jdk-25 JEXTRACT=/path/to/jextract ./scripts/run-all.sh +set -uo pipefail +unset JAVA_TOOL_OPTIONS # the JVM prints a "Picked up JAVA_TOOL_OPTIONS" banner in some sandboxes; it is noise in a transcript +HERE="$(cd "$(dirname "$0")/.." && pwd)"; cd "$HERE" +JAVA_HOME="${JAVA_HOME:?set JAVA_HOME to a JDK 25}"; export PATH="$JAVA_HOME/bin:$PATH" +JEXTRACT="${JEXTRACT:-$HERE/tools/jextract-25/bin/jextract}" +SQLITE_H="${SQLITE_H:-/usr/include/sqlite3.h}" +BUILD="${BUILD:-$HERE/build}"; OUT="$HERE/output"; mkdir -p "$OUT" "$BUILD/classes" +NA="--enable-native-access=ALL-UNNAMED" +run() { echo "\$ java $NA -cp build/classes demo.$1${2:+ $2}"; java $NA -cp "$BUILD/classes" demo."$1" ${2:-} 2>&1; } + +# 01 - what was installed +{ + echo "\$ jextract --version"; "$JEXTRACT" --version 2>&1 + echo "\$ java -version"; java -version 2>&1 + echo "\$ gcc --version | head -1"; gcc --version | head -1 + echo "\$ dpkg-query -W libsqlite3-dev libsqlite3-0"; dpkg-query -W libsqlite3-dev libsqlite3-0 + echo "\$ grep -E '#define SQLITE_(VERSION|VERSION_NUMBER) ' $SQLITE_H"; grep -E '#define SQLITE_(VERSION|VERSION_NUMBER) ' "$SQLITE_H" +} > "$OUT/01-tools.txt" + +# 02 - generate bindings: the trimmed set that is committed, and the whole header for size comparison +{ + echo "\$ scripts/gen.sh" + JEXTRACT="$JEXTRACT" SQLITE_H="$SQLITE_H" scripts/gen.sh 2>&1 + echo "\$ find generated -name '*.java' | wc -l"; find generated -name '*.java' | wc -l + echo "\$ wc -l \$(find generated -name '*.java') | sort -n"; wc -l $(find generated -name '*.java') | sort -n + F="$(mktemp -d)" + echo "\$ jextract --output -t demo.sqlite.full -l sqlite3 $SQLITE_H # no filters: the whole header" + "$JEXTRACT" --output "$F" -t demo.sqlite.full -l sqlite3 "$SQLITE_H" > "$F.log" 2>&1 + echo "warnings: $(grep -c warning "$F.log")"; grep 'Skipping sqlite3 ' "$F.log"; grep 'Skipping sqlite3_stmt ' "$F.log" + echo "files: $(find "$F" -name '*.java' | wc -l)" + echo "lines: $(cat $(find "$F" -name '*.java') | wc -l)" + echo "bytes: $(cat $(find "$F" -name '*.java') | wc -c)" + echo "lines in the one big header class: $(wc -l < "$F/demo/sqlite/full/sqlite3_h.java")" + rm -rf "$F" "$F.log" + echo "\$ grep -o 'public static [A-Za-z.]* sqlite3_open[a-z_0-9\$]*(' generated/demo/sqlite/sqlite3_h.java | sort -u" + grep -o 'public static [A-Za-z.]* sqlite3_open[a-z_0-9$]*(' generated/demo/sqlite/sqlite3_h.java | sort -u +} > "$OUT/02-generate.txt" + +# compile generated + hand-written sources +rm -rf "$BUILD/classes"; mkdir -p "$BUILD/classes" +javac -d "$BUILD/classes" $(find generated src -name '*.java') || exit 1 + +{ run Hello; echo; echo "\$ java $NA -Djextract.trace.downcalls=true -cp build/classes demo.Hello"; java $NA -Djextract.trace.downcalls=true -cp "$BUILD/classes" demo.Hello 2>&1; } > "$OUT/03-hello.txt" + +{ + echo "\$ java -cp build/classes demo.Hello # no flag at all, JDK 25 default" + java -cp "$BUILD/classes" demo.Hello 2>&1 + echo; echo "\$ java --illegal-native-access=deny -cp build/classes demo.Hello" + java --illegal-native-access=deny -cp "$BUILD/classes" demo.Hello 2>&1 | head -6 +} > "$OUT/04-native-access.txt" + +run Tour > "$OUT/05-tour.txt" +run Arenas > "$OUT/06-arenas.txt" +{ + run Trap; echo + echo "\$ java $NA -XX:ErrorFile=/tmp/jextract-hs_err.log -cp build/classes demo.NullText # first lines of the crash banner" + java $NA -XX:ErrorFile=/tmp/jextract-hs_err.log -cp "$BUILD/classes" demo.NullText 2>&1 | sed -n '1,9p' + echo "exit code: ${PIPESTATUS[0]}" +} > "$OUT/07-pointer-traps.txt" +run Errors > "$OUT/08-errors.txt" +{ run Callbacks; echo; echo "\$ java $NA -cp build/classes demo.Callbacks explode"; java $NA -cp "$BUILD/classes" demo.Callbacks explode 2>&1; echo "exit code: $?"; } > "$OUT/09-callbacks.txt" +# 11 - how the -l option decides what gets loaded +{ + T="$(mktemp -d)" + n=0 + for lib in sqlitez :libsqlite3.so.0; do + n=$((n+1)) + echo "\$ jextract -t demo.sqlite -l $lib --include-function sqlite3_libversion --header-class-name sqlite3_h $SQLITE_H" + "$JEXTRACT" --output "$T/gen$n" -t demo.sqlite -l "$lib" --include-function sqlite3_libversion --header-class-name sqlite3_h "$SQLITE_H" 2>&1 + grep -h 'libraryLookup' "$T/gen$n/demo/sqlite/sqlite3_h.java" | sed 's/^ *//' + mkdir -p "$T/c$n"; javac -d "$T/c$n" "$T/gen$n"/demo/sqlite/*.java src/extra/V.java + echo "\$ java $NA -cp V" + java $NA -cp "$T/c$n" V 2>&1 | head -5 + echo + done + rm -rf "$T" +} > "$OUT/11-library-loading.txt" +echo done diff --git a/jextract/scripts/run-jmh.sh b/jextract/scripts/run-jmh.sh new file mode 100755 index 0000000..7514f44 --- /dev/null +++ b/jextract/scripts/run-jmh.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Builds everything and runs the JMH comparison (FFM vs JNI vs plain Java). About 4 minutes. Writes output/10-jmh.txt. +# Needs: JAVA_HOME (JDK 25), gcc, libsqlite3-dev, and `scripts/fetch-jmh.sh` run once. JEXTRACT is only needed by gen.sh, not here. +set -uo pipefail +unset JAVA_TOOL_OPTIONS +HERE="$(cd "$(dirname "$0")/.." && pwd)"; cd "$HERE" +JAVA_HOME="${JAVA_HOME:?set JAVA_HOME to a JDK 25}" +BUILD="${BUILD:-$HERE/build}"; mkdir -p "$BUILD/classes" "$HERE/output" +"$HERE/scripts/build-jni.sh" >/dev/null +CP="$(ls bench/lib/*.jar | tr '\n' ':')" +"$JAVA_HOME/bin/javac" -cp "$CP:$BUILD/jni-classes" -processorpath "$CP" -d "$BUILD/classes" \ + $(find generated src bench/src -name '*.java') 2>&1 | grep -v '^Note:' +OPTS=(-f 5 -wi 3 -w 1 -i 5 -r 1) +{ + echo "\$ java --enable-native-access=ALL-UNNAMED -Djava.library.path=build/lib org.openjdk.jmh.Main ${OPTS[*]} ($("$JAVA_HOME/bin/java" -version 2>&1 | sed -n 2p | sed 's/.*(build \(.*\))/\1/'), JMH 1.37)" + echo "machine: $(nproc) CPUs, $(grep -m1 'model name' /proc/cpuinfo | sed 's/.*: //')" + echo "load average before the run: $(cut -d" " -f1-3 /proc/loadavg) (1, 5, 15 minutes; this box has 2 CPUs and was shared with other jobs)" + echo "sqlite: $(dpkg-query -W -f='${Package} ${Version}' libsqlite3-dev 2>/dev/null)" + "$JAVA_HOME/bin/java" -cp "$BUILD/classes:$BUILD/jni-classes:$CP" org.openjdk.jmh.Main "${OPTS[@]}" \ + -jvmArgs "--enable-native-access=ALL-UNNAMED -Djava.library.path=$BUILD/lib" 2>&1 | sed -n '/^Benchmark /,$p' + echo "load average after the run: $(cut -d' ' -f1-3 /proc/loadavg)" +} > "$HERE/output/10-jmh.txt" +cat "$HERE/output/10-jmh.txt" diff --git a/jextract/src/demo/Arenas.java b/jextract/src/demo/Arenas.java new file mode 100644 index 0000000..c21a6ee --- /dev/null +++ b/jextract/src/demo/Arenas.java @@ -0,0 +1,65 @@ +package demo; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +/** The four arena kinds, and what each one does when you use it wrongly. Every message is real. */ +public class Arenas { + static void show(String label, Runnable r) { + try { + r.run(); + System.out.println(label + " -> ok"); + } catch (Throwable t) { + System.out.println(label + " -> " + t.getClass().getSimpleName() + ": " + t.getMessage()); + } + } + + static void inThread(Runnable r) { + Thread t = new Thread(r, "other"); + t.start(); + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + System.out.println("== confined: one owner thread"); + Arena confined = Arena.ofConfined(); + MemorySegment c = confined.allocate(8); + c.set(ValueLayout.JAVA_LONG, 0, 42L); + show("owner reads", () -> c.get(ValueLayout.JAVA_LONG, 0)); + inThread(() -> show("other thread reads", () -> c.get(ValueLayout.JAVA_LONG, 0))); + inThread(() -> show("other thread closes", confined::close)); + confined.close(); + show("read after close", () -> c.get(ValueLayout.JAVA_LONG, 0)); + show("close twice", confined::close); + + System.out.println("== shared: any thread, one close"); + Arena shared = Arena.ofShared(); + MemorySegment s = shared.allocate(8); + s.set(ValueLayout.JAVA_LONG, 0, 7L); + inThread(() -> show("other thread reads", () -> s.get(ValueLayout.JAVA_LONG, 0))); + inThread(() -> show("other thread closes", shared::close)); + show("read after close", () -> s.get(ValueLayout.JAVA_LONG, 0)); + + System.out.println("== auto: the garbage collector frees it; close() is not allowed"); + Arena auto = Arena.ofAuto(); + show("allocate 8 bytes", () -> auto.allocate(8)); + show("close()", auto::close); + + System.out.println("== global: never freed; close() is not allowed"); + show("allocate 8 bytes", () -> Arena.global().allocate(8)); + show("close()", () -> Arena.global().close()); + + System.out.println("== bounds: a segment knows its size"); + try (Arena a = Arena.ofConfined()) { + MemorySegment four = a.allocate(4); + show("read int at offset 0", () -> four.get(ValueLayout.JAVA_INT, 0)); + show("read int at offset 4", () -> four.get(ValueLayout.JAVA_INT, 4)); + show("read long at offset 0", () -> four.get(ValueLayout.JAVA_LONG, 0)); + } + } +} diff --git a/jextract/src/demo/Callbacks.java b/jextract/src/demo/Callbacks.java new file mode 100644 index 0000000..ff651c4 --- /dev/null +++ b/jextract/src/demo/Callbacks.java @@ -0,0 +1,43 @@ +package demo; + +import static demo.sqlite.sqlite3_h.*; + +import demo.sqlite.sqlite3_exec$callback; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +/** + * sqlite3_exec calls a C function pointer once per result row. jextract generated an interface + * for that pointer type; a lambda implements it, and allocate() turns the lambda into a real C function pointer. + */ +public class Callbacks { + public static void main(String[] args) { + boolean explode = args.length > 0 && args[0].equals("explode"); + try (Db db = Db.open(":memory:"); Arena arena = Arena.ofConfined()) { + db.exec("CREATE TABLE t(id INTEGER, name TEXT)"); + db.exec("INSERT INTO t VALUES (1,'one'),(2,'two'),(3,NULL)"); + + sqlite3_exec$callback.Function onRow = (user, argc, values, names) -> { + if (explode) { + throw new IllegalStateException("thrown inside the callback"); + } + // values and names are char** : argc pointers each. Read them as an array of pointers. + MemorySegment vals = values.reinterpret((long) argc * ValueLayout.ADDRESS.byteSize()); + MemorySegment nms = names.reinterpret((long) argc * ValueLayout.ADDRESS.byteSize()); + StringBuilder sb = new StringBuilder("row:"); + for (int i = 0; i < argc; i++) { + MemorySegment v = vals.getAtIndex(C_POINTER, i); + String value = v.address() == 0 ? "NULL" : v.getString(0); + sb.append(' ').append(nms.getAtIndex(C_POINTER, i).getString(0)).append('=').append(value); + } + System.out.println(sb); + return 0; // non-zero would abort the query with SQLITE_ABORT + }; + MemorySegment stub = sqlite3_exec$callback.allocate(onRow, arena); // the C function pointer + MemorySegment errOut = arena.allocate(C_POINTER); + int rc = sqlite3_exec(db.handle(), arena.allocateFrom("SELECT id, name FROM t"), stub, MemorySegment.NULL, errOut); + System.out.println("sqlite3_exec returned " + rc); + } + } +} diff --git a/jextract/src/demo/Db.java b/jextract/src/demo/Db.java new file mode 100644 index 0000000..14714c9 --- /dev/null +++ b/jextract/src/demo/Db.java @@ -0,0 +1,155 @@ +package demo; + +import static demo.sqlite.sqlite3_h.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.ArrayList; +import java.util.List; + +/** + * A small hand-written layer over the jextract-generated {@code sqlite3_h} class. + * Everything below the {@code public} methods is plain FFM; nothing here is generated. + */ +public final class Db implements AutoCloseable { + private final MemorySegment handle; // the sqlite3* — owned by SQLite, NOT by any Arena + + private Db(MemorySegment handle) { + this.handle = handle; + } + + public static Db open(String path) { + return open(path, SQLITE_OPEN_READWRITE() | SQLITE_OPEN_CREATE()); + } + + public static Db open(String path, int flags) { + // A confined arena: the two small native buffers below live exactly as long as this block. + try (Arena arena = Arena.ofConfined()) { + MemorySegment out = arena.allocate(C_POINTER); // room for ONE pointer: the sqlite3** out-parameter + MemorySegment cPath = arena.allocateFrom(path); // Java String -> NUL-terminated UTF-8 C string + int rc = sqlite3_open_v2(cPath, out, flags, MemorySegment.NULL); + MemorySegment db = out.get(C_POINTER, 0); // read the pointer SQLite wrote + if (rc != SQLITE_OK()) { + // SQLite hands back a handle even on failure; it carries the message and must still be closed. + String message = sqlite3_errmsg(db).getString(0); + sqlite3_close(db); + throw new SqliteException(rc, message); + } + return new Db(db); + } + } + + /** Runs SQL that returns no rows (DDL, INSERT...). Uses sqlite3_exec without a callback. */ + public void exec(String sql) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment errOut = arena.allocate(C_POINTER); // char** errmsg, SQLite fills it only on failure + int rc = sqlite3_exec(handle, arena.allocateFrom(sql), MemorySegment.NULL, MemorySegment.NULL, errOut); + if (rc != SQLITE_OK()) { + MemorySegment msg = errOut.get(C_POINTER, 0); + String text = msg.address() == 0 ? errmsg() : msg.getString(0); + sqlite3_free(msg); // this string came from sqlite3_malloc: we must free it + throw new SqliteException(rc, text); + } + } + } + + /** Runs a SELECT and returns every row as a list of strings. */ + public List> query(String sql) { + try (Arena arena = Arena.ofConfined(); Stmt st = prepare(arena, sql)) { + List> rows = new ArrayList<>(); + int cols = sqlite3_column_count(st.ptr); + int rc; + while ((rc = sqlite3_step(st.ptr)) == SQLITE_ROW()) { + List row = new ArrayList<>(cols); + for (int i = 0; i < cols; i++) { + row.add(text(st.ptr, i)); + } + rows.add(row); + } + if (rc != SQLITE_DONE()) { + throw new SqliteException(rc, errmsg()); + } + return rows; + } + } + + /** Column i of the current row as a String; null for SQL NULL. */ + static String text(MemorySegment stmt, int i) { + if (sqlite3_column_type(stmt, i) == SQLITE_NULL()) { + return null; // sqlite3_column_text would return a NULL pointer here + } + return sqlite3_column_text(stmt, i).getString(0); + } + + /** A prepared statement. The sqlite3_stmt* belongs to SQLite, so it needs an explicit finalize. */ + public final class Stmt implements AutoCloseable { + final MemorySegment ptr; + + public MemorySegment ptr() { + return ptr; + } + + Stmt(MemorySegment ptr) { + this.ptr = ptr; + } + + public void bindInt(int index, int value) { + check(sqlite3_bind_int(ptr, index, value)); + } + + /** SQLITE_TRANSIENT tells SQLite to copy the bytes now, so the temporary C string may be freed straight away. */ + public void bindText(Arena arena, int index, String value) { + check(sqlite3_bind_text(ptr, index, arena.allocateFrom(value), -1, SQLITE_TRANSIENT())); + } + + public boolean step() { + int rc = sqlite3_step(ptr); + if (rc == SQLITE_ROW()) return true; + if (rc == SQLITE_DONE()) return false; + throw new SqliteException(rc, errmsg()); + } + + public void reset() { + sqlite3_reset(ptr); + } + + @Override + public void close() { + sqlite3_finalize(ptr); + } + } + + public Stmt prepare(Arena arena, String sql) { + MemorySegment out = arena.allocate(C_POINTER); + int rc = sqlite3_prepare_v2(handle, arena.allocateFrom(sql), -1, out, MemorySegment.NULL); + check(rc); + return new Stmt(out.get(C_POINTER, 0)); + } + + public int changes() { + return sqlite3_changes(handle); + } + + public long lastInsertRowid() { + return sqlite3_last_insert_rowid(handle); + } + + public String errmsg() { + return sqlite3_errmsg(handle).getString(0); + } + + public MemorySegment handle() { + return handle; + } + + private void check(int rc) { + if (rc != SQLITE_OK()) { + throw new SqliteException(rc, errmsg()); + } + } + + @Override + public void close() { + sqlite3_close(handle); + } +} diff --git a/jextract/src/demo/Errors.java b/jextract/src/demo/Errors.java new file mode 100644 index 0000000..2759910 --- /dev/null +++ b/jextract/src/demo/Errors.java @@ -0,0 +1,29 @@ +package demo; + +import static demo.sqlite.sqlite3_h.*; + +/** Return codes become exceptions. Each failure below is real SQLite output. */ +public class Errors { + static void attempt(String label, Runnable r) { + try { + r.run(); + System.out.println(label + " -> no error"); + } catch (SqliteException e) { + System.out.println(label + " -> " + e.getMessage()); + } + } + + public static void main(String[] args) { + attempt("open in a directory that does not exist", () -> Db.open("/no/such/dir/app.db").close()); + attempt("open a missing file read-only", () -> Db.open("/tmp/jextract-demo-missing.db", SQLITE_OPEN_READONLY()).close()); + try (Db db = Db.open(":memory:")) { + attempt("syntax error", () -> db.exec("SELEC 1")); + attempt("missing table", () -> db.exec("INSERT INTO ghost VALUES (1)")); + db.exec("CREATE TABLE t(id INTEGER PRIMARY KEY, name TEXT NOT NULL)"); + attempt("NOT NULL violation", () -> db.exec("INSERT INTO t(id) VALUES (1)")); + db.exec("INSERT INTO t VALUES (1, 'a')"); + attempt("PRIMARY KEY violation", () -> db.exec("INSERT INTO t VALUES (1, 'b')")); + System.out.println("SQLITE_CONSTRAINT = " + SQLITE_CONSTRAINT() + ", sqlite3_errstr(19) = " + sqlite3_errstr(19).getString(0)); + } + } +} diff --git a/jextract/src/demo/Hello.java b/jextract/src/demo/Hello.java new file mode 100644 index 0000000..467c870 --- /dev/null +++ b/jextract/src/demo/Hello.java @@ -0,0 +1,16 @@ +package demo; + +import static demo.sqlite.sqlite3_h.*; + +import java.lang.foreign.MemorySegment; + +/** The smallest useful call: ask the native library which version it is. */ +public class Hello { + public static void main(String[] args) { + MemorySegment v = sqlite3_libversion(); // const char* -> MemorySegment + System.out.println("sqlite3_libversion() = " + v.getString(0)); + System.out.println("returned segment byteSize = " + v.byteSize()); + System.out.println("sqlite3_libversion_number() = " + sqlite3_libversion_number()); + System.out.println("sqlite3_sourceid() = " + sqlite3_sourceid().getString(0)); + } +} diff --git a/jextract/src/demo/NullText.java b/jextract/src/demo/NullText.java new file mode 100644 index 0000000..8f98680 --- /dev/null +++ b/jextract/src/demo/NullText.java @@ -0,0 +1,28 @@ +package demo; + +import static demo.sqlite.sqlite3_h.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +/** What sqlite3_column_text returns for a SQL NULL, and what happens if you call getString(0) on it anyway. */ +public class NullText { + public static void main(String[] args) { + try (Db db = Db.open(":memory:"); Arena arena = Arena.ofConfined()) { + db.exec("CREATE TABLE t(a TEXT)"); + db.exec("INSERT INTO t VALUES (NULL)"); + try (Db.Stmt st = db.prepare(arena, "SELECT a FROM t")) { + st.step(); + MemorySegment p = sqlite3_column_text(st.ptr(), 0); + System.out.println("address=" + p.address() + " byteSize=" + p.byteSize() + " equals(NULL)=" + p.equals(MemorySegment.NULL)); + System.out.println("about to call getString(0) on it"); + System.out.flush(); + try { + System.out.println("getString -> " + p.getString(0)); + } catch (Throwable t) { + System.out.println("getString -> " + t.getClass().getSimpleName() + ": " + t.getMessage()); + } + } + } + } +} diff --git a/jextract/src/demo/SqliteException.java b/jextract/src/demo/SqliteException.java new file mode 100644 index 0000000..120b84b --- /dev/null +++ b/jextract/src/demo/SqliteException.java @@ -0,0 +1,15 @@ +package demo; + +/** A SQLite result code turned into an exception: the numeric code plus the text from sqlite3_errmsg. */ +public final class SqliteException extends RuntimeException { + private final int code; + + public SqliteException(int code, String message) { + super(message + " (rc=" + code + ")"); + this.code = code; + } + + public int code() { + return code; + } +} diff --git a/jextract/src/demo/Tour.java b/jextract/src/demo/Tour.java new file mode 100644 index 0000000..a20960b --- /dev/null +++ b/jextract/src/demo/Tour.java @@ -0,0 +1,28 @@ +package demo; + +import java.lang.foreign.Arena; + +/** Open a database, create a table, insert with bind parameters, read it back. */ +public class Tour { + public static void main(String[] args) { + try (Db db = Db.open(":memory:")) { + db.exec("CREATE TABLE person(id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER)"); + try (Arena arena = Arena.ofConfined(); Db.Stmt ins = db.prepare(arena, "INSERT INTO person(name, age) VALUES (?1, ?2)")) { + String[] names = {"Ankur", "Asha", "Ravi"}; + int[] ages = {41, 36, 29}; + for (int i = 0; i < names.length; i++) { + ins.bindText(arena, 1, names[i]); + ins.bindInt(2, ages[i]); + ins.step(); + ins.reset(); + } + } + System.out.println("rows changed by last statement: " + db.changes() + ", last rowid: " + db.lastInsertRowid()); + for (var row : db.query("SELECT id, name, age FROM person ORDER BY id")) { + System.out.println(row); + } + db.exec("INSERT INTO person(name) VALUES ('NoAge')"); + System.out.println(db.query("SELECT name, age FROM person WHERE age IS NULL")); + } + } +} diff --git a/jextract/src/demo/Trap.java b/jextract/src/demo/Trap.java new file mode 100644 index 0000000..d272eec --- /dev/null +++ b/jextract/src/demo/Trap.java @@ -0,0 +1,33 @@ +package demo; + +import static demo.sqlite.sqlite3_h.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +/** + * The trap that costs the first hour: a pointer you read out of memory is a zero-length segment + * unless its layout says what it points at. jextract's C_POINTER says "unbounded chars"; plain ADDRESS does not. + */ +public class Trap { + public static void main(String[] args) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment slot = arena.allocate(ValueLayout.ADDRESS); + slot.set(ValueLayout.ADDRESS, 0, arena.allocateFrom("hello from C land")); + + MemorySegment viaAddress = slot.get(ValueLayout.ADDRESS, 0); + System.out.println("ValueLayout.ADDRESS : byteSize=" + viaAddress.byteSize()); + try { + viaAddress.getString(0); + } catch (IndexOutOfBoundsException e) { + System.out.println("getString(0) : IndexOutOfBoundsException, \"" + e.getMessage().substring(0, e.getMessage().indexOf(':', 22)) + "...\""); + } + System.out.println("reinterpret(100) : " + viaAddress.reinterpret(100).getString(0)); + + MemorySegment viaCPointer = slot.get(C_POINTER, 0); + System.out.println("C_POINTER : byteSize=" + viaCPointer.byteSize()); + System.out.println("getString(0) : " + viaCPointer.getString(0)); + } + } +} diff --git a/jextract/src/extra/V.java b/jextract/src/extra/V.java new file mode 100644 index 0000000..e32d947 --- /dev/null +++ b/jextract/src/extra/V.java @@ -0,0 +1,8 @@ +// Used only by scripts/run-all.sh to test library loading with throwaway bindings (package demo.sqlite, one function). +import static demo.sqlite.sqlite3_h.*; + +public class V { + public static void main(String[] a) { + System.out.println("sqlite3_libversion() = " + sqlite3_libversion().getString(0)); + } +}