Initial check-in of JNI (Java Native Interface) bindings for the core C API.

FossilOrigin-Name: b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5
This commit is contained in:
stephan 2023-07-27 20:02:49 +00:00
parent 9cf8961557
commit dff3443939
19 changed files with 6151 additions and 8 deletions

168
ext/jni/GNUmakefile Normal file
View File

@ -0,0 +1,168 @@
# Quick-and-dirty makefile to bootstrap the sqlite3-jni project. This
# build assumes a Linux-like system.
default: all
JDK_HOME ?= $(HOME)/jdk/current
# /usr/lib/jvm/default-javajava-19-openjdk-amd64
bin.javac := $(JDK_HOME)/bin/javac
bin.java := $(JDK_HOME)/bin/java
bin.jar := $(JDK_HOME)/bin/jar
ifeq (,$(wildcard $(JDK_HOME)))
$(error set JDK_HOME to the top-most dir of your JDK installation.)
endif
MAKEFILE := $(lastword $(MAKEFILE_LIST))
$(MAKEFILE):
package.version := 0.0.1
package.jar := sqlite3-jni-$(package.version).jar
dir.top := ../..
dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE)))
dir.src := $(dir.jni)/src
dir.src.c := $(dir.src)/c
dir.bld := $(dir.jni)/bld
dir.bld.c := $(dir.bld)
dir.src.jni := $(dir.src)/org/sqlite/jni
$(dir.bld.c):
mkdir -p $@
classpath := $(dir.src)
CLEAN_FILES := $(package.jar)
DISTCLEAN_FILES := $(dir.jni)/*~ $(dir.src.c)/*~ $(dir.src.jni)/*~
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
.NOTPARALLEL: $(sqlite3-jni.h)
SQLite3Jni.java := src/org/sqlite/jni/SQLite3Jni.java
SQLite3Jni.class := $(subst .java,.class,$(SQLite3Jni.java))
#$(sqlite3-jni.h): $(SQLite3Jni.java) $(dir.bld.c)
# $(bin.javac) -h $(dir $@) $<
#all: $(sqlite3-jni.h)
JAVA_FILES := $(wildcard $(dir.src.jni)/*.java)
CLASS_FILES :=
define DOTCLASS_DEPS
$(1).class: $(1).java
all: $(1).class
CLASS_FILES += $(1).class
endef
$(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B))))
javac.flags ?= -Xlint:unchecked -Xlint:deprecation
java.flags ?=
jnicheck ?= 1
ifeq (1,$(jnicheck))
java.flags += -Xcheck:jni
endif
$(SQLite3Jni.class): $(JAVA_FILES)
$(bin.javac) $(javac.flags) -h $(dir.bld.c) -cp $(classpath) $(JAVA_FILES)
all: $(SQLite3Jni.class)
#.PHONY: classfiles
########################################################################
# Set up sqlite3.c and sqlite3.h...
#
# To build with SEE (https://sqlite.org/see), either put sqlite3-see.c
# in the top of this build tree or pass
# sqlite3.c=PATH_TO_sqlite3-see.c to the build. Note that only
# encryption modules with no 3rd-party dependencies will currently
# work here: AES256-OFB, AES128-OFB, and AES128-CCM. Not
# coincidentally, those 3 modules are included in the sqlite3-see.c
# bundle.
#
# A custom sqlite3.c must not have any spaces in its name.
# $(sqlite3.canonical.c) must point to the sqlite3.c in
# the sqlite3 canonical source tree, as that source file
# is required for certain utility and test code.
sqlite3.canonical.c := $(dir.top)/sqlite3.c
sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c))
sqlite3.h := $(dir.top)/sqlite3.h
#ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null))
# SQLITE_C_IS_SEE := 0
#else
# SQLITE_C_IS_SEE := 1
# $(info This is an SEE build.)
#endif
.NOTPARALLEL: $(sqlite3.h)
$(sqlite3.h):
$(MAKE) -C $(dir.top) sqlite3.c
$(sqlite3.c): $(sqlite3.h)
SQLITE_OPT := \
-DSQLITE_ENABLE_FTS5 \
-DSQLITE_ENABLE_RTREE \
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
-DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
-DSQLITE_ENABLE_STMTVTAB \
-DSQLITE_ENABLE_DBPAGE_VTAB \
-DSQLITE_ENABLE_DBSTAT_VTAB \
-DSQLITE_ENABLE_BYTECODE_VTAB \
-DSQLITE_ENABLE_OFFSET_SQL_FUNC \
-DSQLITE_OMIT_LOAD_EXTENSION \
-DSQLITE_OMIT_DEPRECATED \
-DSQLITE_OMIT_SHARED_CACHE \
-DSQLITE_OMIT_WAL \
-DSQLITE_THREADSAFE=0 \
-DSQLITE_TEMP_STORE=2 \
-DSQLITE_USE_URI=1 \
-DSQLITE_C=$(sqlite3.c) \
-DSQLITE_DEBUG
# -DSQLITE_DEBUG is just to work around a -Wall warning
# for a var which gets set in all builds but only read
# via assert().
sqlite3-jni.c := $(dir.src.c)/sqlite3-jni.c
sqlite3-jni.o := $(dir.bld.c)/sqlite3-jni.o
sqlite3-jni.h.in := $(dir.bld.c)/org_sqlite_jni_SQLite3Jni.h
sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h
sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so
# ------------------------------^^^ lib prefix is requires
# for java's System.loadLibrary().
#sqlite3-jni.dll.cfiles := $(dir.src.c)
sqlite3-jni.dll.cflags := \
-fPIC \
-I. \
-I$(dir $(sqlite3.h)) \
-I$(dir.src.c) \
-I$(JDK_HOME)/include \
$(patsubst %,-I%,$(patsubst %.h,,$(wildcard $(JDK_HOME)/include/*))) \
-Wall
# Using (-Wall -Wextra) triggers an untennable number of
# gcc warnings from sqlite3.c for mundane things like
# unused parameters.
#
# The gross $(patsubst...) above is to include the platform-specific
# subdir which lives under $(JDK_HOME)/include and is a required
# include path for client-level code.
########################################################################
$(sqlite3-jni.h.in): $(dir.src.jni)/SQLite3Jni.class
$(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE)
cp -p $(sqlite3-jni.h.in) $@
$(sqlite3-jni.c): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h)
$(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE)
$(CC) $(sqlite3-jni.dll.cflags) $(SQLITE_OPT) \
$(sqlite3-jni.c) -shared -o $@
all: $(sqlite3-jni.dll)
test: $(SQLite3Jni.class) $(sqlite3-jni.dll)
$(bin.java) -ea -Djava.library.path=$(dir.bld.c) \
$(java.flags) -cp $(classpath) org.sqlite.jni.Tester1
$(package.jar): $(CLASS_FILES) $(MAKEFILE)
rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~
$(bin.jar) -cfe $@ org.sqlite.Tester1 -C src org -C src c
jar: $(package.jar)
CLEAN_FILES += $(dir.bld.c)/* \
$(dir.src.jni)/*.class \
$(sqlite3-jni.dll) \
hs_err_pid*.log
.PHONY: clean distclean
clean:
-rm -f $(CLEAN_FILES)
distclean: clean
-rm -f $(DISTCLEAN_FILES)
-rm -fr $(dir.bld.c)

213
ext/jni/README.md Normal file
View File

@ -0,0 +1,213 @@
SQLite3 via JNI
========================================================================
This repository houses a Java Native Interface (JNI) binding for the
sqlite3 API.
> **FOREWARNING:** this project is very much in development and
subject to any number of changes. Please do not rely on any
information about its API until this disclaimer is removed.
Project goals/requirements:
- A [1-to-1(-ish) mapping of the C API](#1to1ish) to Java via JNI,
insofar as cross-language semantics allow for. A closely-related
goal is that [the C documentation](www:/c3ref/intro.html)
should be usable as-is, insofar as possible, for the JNI binding.
- Support Java as far back as version 8 (2014).
- Environment-independent. Should work everywhere both Java
and SQLite3 do.
- No 3rd-party dependencies beyond the JDK. That includes no
build-level dependencies for specific IDEs and toolchains. We
welcome the addition of build files for arbitrary environments
insofar as they do not directly interfere with each other.
Non-goals:
- Creation of high-level OO wrapper APIs. Clients are free to create
them off of the C-style API.
Significant TODOs
========================================================================
- LOTS of APIs left to bind.
- Bundling of the resulting class files into a jar. Bundling the DLLs
is a much larger problem, as they inherently have platform-specific
OS-level dependencies which we obviously cannot bundle.
Building
========================================================================
The canonical builds assumes a Linux-like environment and requires:
- GNU Make
- A JDK supporting Java 8 or higher
- A modern C compiler. gcc and clang should both work.
Put simply:
```
$ export JDK_HOME=/path/to/jdk/root
$ make
$ make test
$ make clean
```
<a id='1to1ish'></a>
One-to-One(-ish) Mapping to C
========================================================================
This JNI binding aims to provide as close to a 1-to-1 experience with
the C API as cross-language semantics allow. Exceptions are
necessarily made where cross-language semantics do not allow a 1-to-1,
and judiciously made where a 1-to-1 mapping would be unduly cumbersome
to use in Java.
Golden Rule: _Never_ Throw from Callbacks
------------------------------------------------------------------------
JNI bindings which accept client-defined functions _must never throw
exceptions_. There are _no exceptions_ to this rule. Exceptions are
reserved for higher-level bindings which are constructed to
specifically deal with them and ensure that they do not leak C-level
resources. Some of the JNI bindings are provided as Java functions
which expect this rule to always hold.
UTF-8(-ish)
------------------------------------------------------------------------
SQLite internally uses UTF-8 encoding, whereas Java natively uses
UTF-16. Java JNI has routines for converting to and from UTF-8, _but_
Java uses what its docs call "[modified UTF-8][modutf8]." Care must be
taken when converting Java strings to UFF-8 to ensure that the proper
conversion is performed. In short,
`String.getBytes(StandardCharsets.UTF_8)` performs the proper
conversion in Java, and there is no JNI C API for that conversion
(JNI's `NewStringUTF()` returns MUTF-8).
[modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8
Unweildy Constructs are Re-mapped
------------------------------------------------------------------------
Some constructs, when modelled 1-to-1 from C to Java, are unduly
clumsy to work with in Java because they try to shoehorn C's way of
doing certain things into Java's wildly different ways. The following
subsections cover those, starting with a verbose explanation and
demonstration of where such changes are "really necessary"...
### Custom Collations
A prime example of where interface changes for Java are necessary for
usability is [registration of a custom
collation](www:/c3ref/create_collation.html):
```
// C:
int sqlite3_create_collation(sqlite3 * db, const char * name, int eTextRep,
void *pUserData,
int (*xCompare)(void*,int,void const *,int,void const *));
int sqlite3_create_collation_v2(sqlite3 * db, const char * name, int eTextRep,
void *pUserData,
int (*xCompare)(void*,int,void const *,int,void const *),
void (*xDestroy)(void*));
```
The `pUserData` object is optional client-defined state for the
`xCompare()` and/or `xDestroy()` callback functions, both of which are
passed that object as their first argument. That data is passed around
"externally" in C because that's how C models the world. If we were to
bind that part as-is to Java, the result would be awkward to use (^Yes,
we tried this.):
```
// Java:
int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
Object pUserData, xCompareType xCompare);
int sqlite3_create_collation_v2(sqlite3 db, String name, int eTextRep,
Object pUserData,
xCompareType xCompare, xDestroyType xDestroy);
```
The awkwardness comes from (A) having two distinctly different objects
for callbacks and (B) having their internal state provided separately,
which is ill-fitting in Java. For the sake of usability, C APIs which
follow that pattern use a slightly different Java interface:
```
int sqlite3_create_collation(sqlite3 db, String name, int eTextRep,
Collation collation);
```
Where the `Collation` class has an abstract `xCompare()` method and
no-op `xDestroy()` method which can be overridden if needed, leading to
a much more Java-esque usage:
```
int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation(){
// Required comparison function:
@Override public int xCompare(byte[] lhs, byte[] rhs){ ... }
// Optional finalizer function:
@Override public void xDestroy(){ ... }
// Optional local state:
private String localState1 =
"This is local state. There are many like it, but this one is mine.";
private MyStateType localState2 = new MyStateType();
...
});
```
Noting that:
- It is still possible to bind in call-scope-local state via closures,
but using member data for the Collation object is generally a better
fit for Java.
- No capabilities of the C API are lost or unduly obscured via the
above API reshaping, so power users need not make any compromises.
- In the specific example above, `sqlite3_create_collation_v2()`
becomes superfluous because the provided interface effectively
provides both the v1 and v2 interfaces, the difference being that
overriding the `xDestroy()` method effectively gives it v2
semantics.
### User-defined SQL Functions (a.k.a. UDFs)
The [`sqlite3_create_function()`](www:/c3ref/create_function.html)
family of APIs make heavy use of function pointers to provide
client-defined callbacks, necessitating interface changes in the JNI
binding. The Jav API has only one core function-registration function:
```
int sqlite3_create_function(sqlite3 db, String funcName, int nArgs,
int encoding, SQLFunction func);
```
`SQLFunction` is not used directly, but is instead instantiated via
one of its three subclasses:
- `SQLFunction.Scalar` implements simple scalar functions using but a
single callback.
- `SQLFunction.Aggregate` implements aggregate functions using two
callbacks.
- `SQLFunction.Window` implements window functions using four
callbacks.
Search [`Tester1.java`](/file/src/org/sqlite/jni/Tester1.java) for
`SQLFunction` for how it's used.
Reminder: see the disclaimer at the top of this document regarding the
in-flux nature of this API.
[jsrc]: /file/
[www]: https://sqlite.org

1935
ext/jni/src/c/sqlite3-jni.c Normal file

File diff suppressed because it is too large Load Diff

1561
ext/jni/src/c/sqlite3-jni.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
/*
** 2023-07-22
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
*/
public abstract class Collation {
/**
Must compare the given byte arrays using memcmp() semantics.
*/
public abstract int xCompare(byte[] lhs, byte[] rhs);
/**
Called by SQLite when the collation is destroyed. If a Collation
requires custom cleanup, override this method.
*/
public void xDestroy() {}
}

View File

@ -0,0 +1,40 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A helper for passing pointers between JNI C code and Java, in
particular for output pointers of high-level object types in the
sqlite3 C API, e.g. (sqlite3**) and (sqlite3_stmt**). This is
intended to be subclassed and the ContextType is intended to be the
class which is doing the subclassing. The intent of the ContextType
is strictly to provide some level of type safety by avoiding that
NativePointerHolder is not inadvertently passed to an incompatible
function signature.
These objects are not intended to _own_ the pointer they refer to.
They are intended to simply communicate that pointer between C and
Java.
*/
public class NativePointerHolder<ContextType> {
private long pointer;
public NativePointerHolder(long pointer){
this.pointer = pointer;
}
public NativePointerHolder(){
this.pointer = 0;
}
public final long getNativePointer(){ return pointer; }
public final void setNativePointer(long p){ pointer = p; }
}

View File

@ -0,0 +1,36 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
Helper classes for handling JNI output pointers for primitive
types. Higher-level classes which use output pointers have their
own corresponding Java class, e.g. sqlite3 and sqlite3_stmt.
We do not use a generic OutputPointer<T> because working with those
from the native JNI code is unduly quirky due to a lack of
autoboxing at that level.
*/
public final class OutputPointer {
public static final class Int32 {
private int value;
public final void setValue(int v){value = v;}
public final int getValue(){return value;}
}
public static final class Int64 {
private long value;
public final void setValue(long v){value = v;}
public final long getValue(){return value;}
}
}

View File

@ -0,0 +1,23 @@
/*
** 2023-07-22
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
Callback proxy for use with sqlite3_progress_handler().
*/
public interface ProgressHandler {
/**
*/
int xCallback();
}

View File

@ -0,0 +1,50 @@
/*
** 2023-07-22
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
SQLFunction is used in conjunction with the
sqlite3_create_function() JNI-bound API to give that native code
access to the callback functions needed in order to implement SQL
functions in Java. This class is not used by itself: see the
three inner classes.
*/
public abstract class SQLFunction {
//! Subclass for creating scalar functions.
public static abstract class Scalar extends SQLFunction {
public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args);
/**
Optionally override to be notified when the function is
finalized by SQLite.
*/
public void xDestroy() {}
}
//! Subclass for creating aggregate functions.
public static abstract class Aggregate extends SQLFunction {
public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
public abstract void xFinal(sqlite3_context cx);
public void xDestroy() {}
}
//! Subclass for creating window functions.
public static abstract class Window extends SQLFunction {
public abstract void xStep(sqlite3_context cx, sqlite3_value[] args);
public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args);
public abstract void xFinal(sqlite3_context cx);
public abstract void xValue(sqlite3_context cx);
public void xDestroy() {}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,675 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file contains a set of tests for the sqlite3 JNI bindings.
** They make heavy use of assert(), so must be run with java's -ea
** (enble assert) flag.
*/
package org.sqlite.jni;
import static org.sqlite.jni.SQLite3Jni.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class Tester1 {
private static <T> void out(T val){
System.out.print(val);
}
private static <T> void outln(T val){
System.out.println(val);
}
private static int assertCount = 0;
private static void myassert(Boolean v){
++assertCount;
assert( v );
}
private static void test1(){
outln("libversion_number: "
+ sqlite3_libversion_number()
+ "\n"
+ sqlite3_libversion()
+ "\n"
+ SQLITE_SOURCE_ID);
myassert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER);
//outln("threadsafe = "+sqlite3_threadsafe());
myassert(SQLITE_MAX_LENGTH > 0);
myassert(SQLITE_MAX_TRIGGER_DEPTH>0);
}
private static void testCompileOption(){
int i = 0;
String optName;
outln("compile options:");
for( ; null != (optName = sqlite3_compileoption_get(i)); ++i){
outln("\t"+optName+"\t (used="+
sqlite3_compileoption_used(optName)+")");
}
}
private static void execSql(sqlite3 db, String[] sql){
execSql(db, String.join("", sql));
}
private static void execSql(sqlite3 db, String sql){
OutputPointer.Int32 oTail = new OutputPointer.Int32();
final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8);
int pos = 0, n = 1;
byte[] sqlChunk = sqlUtf8;
sqlite3_stmt stmt = new sqlite3_stmt();
while(pos < sqlChunk.length){
if(pos > 0){
sqlChunk = Arrays.copyOfRange(sqlChunk, pos,
sqlChunk.length);
}
if( 0==sqlChunk.length ) break;
int rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail);
myassert(0 == rc);
pos = oTail.getValue();
myassert(0 != stmt.getNativePointer());
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
myassert(0 == stmt.getNativePointer());
if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){
throw new RuntimeException("db op failed with rc="+rc);
}
}
}
private static void testOpenDb1(){
sqlite3 db = new sqlite3();
myassert(0 == db.getNativePointer());
int rc = sqlite3_open(":memory:", db);
myassert(0 == rc);
myassert(0 < db.getNativePointer());
sqlite3_close(db);
myassert(0 == db.getNativePointer());
}
private static void testOpenDb2(){
sqlite3 db = new sqlite3();
myassert(0 == db.getNativePointer());
int rc = sqlite3_open_v2(":memory:", db,
SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE, null);
myassert(0 == rc);
myassert(0 < db.getNativePointer());
sqlite3_close_v2(db);
myassert(0 == db.getNativePointer());
}
private static sqlite3 createNewDb(){
sqlite3 db = new sqlite3();
myassert(0 == db.getNativePointer());
int rc = sqlite3_open(":memory:", db);
myassert(0 == rc);
myassert(0 != db.getNativePointer());
rc = sqlite3_busy_timeout(db, 2000);
myassert( 0 == rc );
return db;
}
private static void testPrepare123(){
sqlite3 db = createNewDb();
int rc;
sqlite3_stmt stmt = new sqlite3_stmt();
myassert(0 == stmt.getNativePointer());
rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", stmt);
myassert(0 == rc);
myassert(0 != stmt.getNativePointer());
rc = sqlite3_step(stmt);
myassert(SQLITE_DONE == rc);
sqlite3_finalize(stmt);
myassert(0 == stmt.getNativePointer());
{ /* Demonstrate how to use the "zTail" option of
sqlite3_prepare() family of functions. */
OutputPointer.Int32 oTail = new OutputPointer.Int32();
final byte[] sqlUtf8 =
"CREATE TABLE t2(a); INSERT INTO t2(a) VALUES(1),(2),(3)"
.getBytes(StandardCharsets.UTF_8);
int pos = 0, n = 1;
byte[] sqlChunk = sqlUtf8;
while(pos < sqlChunk.length){
if(pos > 0){
sqlChunk = Arrays.copyOfRange(sqlChunk, pos, sqlChunk.length);
}
//outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos);
if( 0==sqlChunk.length ) break;
rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail);
myassert(0 == rc);
pos = oTail.getValue();
/*outln("SQL tail pos = "+pos+". Chunk = "+
(new String(Arrays.copyOfRange(sqlChunk,0,pos),
StandardCharsets.UTF_8)));*/
switch(n){
case 1: myassert(19 == pos); break;
case 2: myassert(36 == pos); break;
default: myassert( false /* can't happen */ );
}
++n;
myassert(0 != stmt.getNativePointer());
rc = sqlite3_step(stmt);
myassert(SQLITE_DONE == rc);
sqlite3_finalize(stmt);
myassert(0 == stmt.getNativePointer());
}
}
rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)",
SQLITE_PREPARE_NORMALIZE, stmt);
myassert(0 == rc);
myassert(0 != stmt.getNativePointer());
sqlite3_finalize(stmt);
myassert(0 == stmt.getNativePointer() );
sqlite3_close_v2(db);
}
private static void testBindFetchInt(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = new sqlite3_stmt();
int rc = sqlite3_prepare(db, "INSERT INTO t(a) VALUES(:a);", stmt);
myassert(0 == rc);
myassert(1 == sqlite3_bind_parameter_count(stmt));
final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a");
myassert(1 == paramNdx);
int total1 = 0;
long rowid = -1;
int changes = sqlite3_changes(db);
int changesT = sqlite3_total_changes(db);
long changes64 = sqlite3_changes64(db);
long changesT64 = sqlite3_total_changes64(db);
for(int i = 99; i < 102; ++i ){
total1 += i;
rc = sqlite3_bind_int(stmt, paramNdx, i);
myassert(0 == rc);
rc = sqlite3_step(stmt);
sqlite3_reset(stmt);
myassert(SQLITE_DONE == rc);
long x = sqlite3_last_insert_rowid(db);
myassert(x > rowid);
rowid = x;
}
sqlite3_finalize(stmt);
myassert(total1 > 0);
myassert(sqlite3_changes(db) > changes);
myassert(sqlite3_total_changes(db) > changesT);
myassert(sqlite3_changes64(db) > changes64);
myassert(sqlite3_total_changes64(db) > changesT64);
rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt);
myassert(0 == rc);
int total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
total2 += sqlite3_column_int(stmt, 0);
sqlite3_value sv = sqlite3_column_value(stmt, 0);
myassert( null != sv );
myassert( 0 != sv.getNativePointer() );
myassert( SQLITE_INTEGER == sqlite3_value_type(sv) );
}
sqlite3_finalize(stmt);
myassert(total1 == total2);
sqlite3_close_v2(db);
myassert(0 == db.getNativePointer());
}
private static void testBindFetchInt64(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = new sqlite3_stmt();
int rc = sqlite3_prepare(db, "INSERT INTO t(a) VALUES(?);", stmt);
long total1 = 0;
for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){
total1 += i;
sqlite3_bind_int64(stmt, 1, i);
sqlite3_step(stmt);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt);
myassert(0 == rc);
long total2 = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
total2 += sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
myassert(total1 == total2);
sqlite3_close_v2(db);
}
private static void testBindFetchDouble(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = new sqlite3_stmt();
int rc = sqlite3_prepare(db, "INSERT INTO t(a) VALUES(?);", stmt);
double total1 = 0;
for(double i = 1.5; i < 5.0; i = i + 1.0 ){
total1 += i;
sqlite3_bind_double(stmt, 1, i);
sqlite3_step(stmt);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt);
myassert(0 == rc);
double total2 = 0;
int counter = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
++counter;
total2 += sqlite3_column_double(stmt, 0);
}
myassert(4 == counter);
sqlite3_finalize(stmt);
myassert(total2<=total1+0.01 && total2>=total1-0.01);
sqlite3_close_v2(db);
}
private static void testBindFetchText(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = new sqlite3_stmt();
int rc = sqlite3_prepare(db, "INSERT INTO t(a) VALUES(?);", stmt);
String list1[] = { "hell🤩", "w😃rld", "!" };
for( String e : list1 ){
rc = sqlite3_bind_text(stmt, 1, e);
myassert(0 == rc);
rc = sqlite3_step(stmt);
myassert(SQLITE_DONE==rc);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt);
myassert(0 == rc);
StringBuffer sbuf = new StringBuffer();
int n = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
String txt = sqlite3_column_text(stmt, 0);
//outln("txt = "+txt);
sbuf.append( txt );
++n;
}
sqlite3_finalize(stmt);
myassert(3 == n);
myassert("w😃rldhell🤩!".equals(sbuf.toString()));
sqlite3_close_v2(db);
}
private static void testBindFetchBlob(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a)");
sqlite3_stmt stmt = new sqlite3_stmt();
int rc = sqlite3_prepare(db, "INSERT INTO t(a) VALUES(?);", stmt);
byte list1[] = { 0x32, 0x33, 0x34 };
rc = sqlite3_bind_blob(stmt, 1, list1);
rc = sqlite3_step(stmt);
myassert(SQLITE_DONE == rc);
sqlite3_finalize(stmt);
rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt);
myassert(0 == rc);
int n = 0;
int total = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
byte blob[] = sqlite3_column_blob(stmt, 0);
myassert(3 == blob.length);
int i = 0;
for(byte b : blob){
myassert(b == list1[i++]);
total += b;
}
++n;
}
sqlite3_finalize(stmt);
myassert(1 == n);
myassert(total == 0x32 + 0x33 + 0x34);
sqlite3_close_v2(db);
}
private static void testCollation(){
sqlite3 db = createNewDb();
execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
final ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
final Collation myCollation = new Collation() {
private String myState =
"this is local state. There is much like it, but this is mine.";
@Override
// Reverse-sorts its inputs...
public int xCompare(byte[] lhs, byte[] rhs){
int len = lhs.length > rhs.length ? rhs.length : lhs.length;
int c = 0, i = 0;
for(i = 0; i < len; ++i){
c = lhs[i] - rhs[i];
if(0 != c) break;
}
if(0==c){
if(i < lhs.length) c = 1;
else if(i < rhs.length) c = -1;
}
return -c;
}
@Override
public void xDestroy() {
// Just demonstrates that xDestroy is called.
xDestroyCalled.value = true;
}
};
int rc = sqlite3_create_collation(db, "reversi", SQLITE_UTF8, myCollation);
myassert(0 == rc);
sqlite3_stmt stmt = new sqlite3_stmt();
sqlite3_prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi", stmt);
int counter = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
final String val = sqlite3_column_text(stmt, 0);
++counter;
//outln("REVERSI'd row#"+counter+": "+val);
switch(counter){
case 1: myassert("c".equals(val)); break;
case 2: myassert("b".equals(val)); break;
case 3: myassert("a".equals(val)); break;
}
}
myassert(3 == counter);
sqlite3_finalize(stmt);
sqlite3_prepare(db, "SELECT a FROM t ORDER BY a", stmt);
counter = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
final String val = sqlite3_column_text(stmt, 0);
++counter;
//outln("Non-REVERSI'd row#"+counter+": "+val);
switch(counter){
case 3: myassert("c".equals(val)); break;
case 2: myassert("b".equals(val)); break;
case 1: myassert("a".equals(val)); break;
}
}
myassert(3 == counter);
sqlite3_finalize(stmt);
myassert(!xDestroyCalled.value);
sqlite3_close(db);
myassert(xDestroyCalled.value);
}
private static void testToUtf8(){
/**
Java docs seem contradictory, claiming to use "modified UTF-8"
encoding while also claiming to export using RFC 2279:
https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html
*/
final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8);
//out("\"a NUL b\" via getBytes(): ");
myassert( 5 == ba.length /* as opposed to 6 in modified utf-8 */);
//for( byte b : ba ) out( ""+b );
//outln("");
}
private static void testUdf1(){
final sqlite3 db = createNewDb();
// These ValueHolders are just to confirm that the func did what we want...
final ValueHolder<Boolean> xDestroyCalled = new ValueHolder<>(false);
final ValueHolder<Integer> xFuncAccum = new ValueHolder<>(0);
// Create an SQLFunction instance using one of its 3 subclasses:
// Scalar, Aggregate, or Window:
SQLFunction func =
// Each of the 3 subclasses requires a different set of
// functions, all of which must be implemented. Anonymous
// classes are a convenient way to implement these, though the
// result is possibly somewhat noisy for those not at home in
// Java...
new SQLFunction.Scalar(){
public void xFunc(sqlite3_context cx, sqlite3_value args[]){
myassert(db.getNativePointer()
== sqlite3_context_db_handle(cx).getNativePointer());
int result = 0;
for( sqlite3_value v : args ) result += sqlite3_value_int(v);
xFuncAccum.value += result;// just for post-run testing
sqlite3_result_int(cx, result);
}
/* OPTIONALLY override xDestroy... */
public void xDestroy(){
xDestroyCalled.value = true;
}
};
// Register and use the function...
int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
assert(0 == rc);
assert(0 == xFuncAccum.value);
execSql(db, "SELECT myfunc(1,2,3)");
assert(6 == xFuncAccum.value);
assert( !xDestroyCalled.value );
sqlite3_close(db);
assert( xDestroyCalled.value );
}
private static void testUdfJavaObject(){
final sqlite3 db = createNewDb();
final ValueHolder<Long> testResult = new ValueHolder<>(42L);
SQLFunction func = new SQLFunction.Scalar(){
public void xFunc(sqlite3_context cx, sqlite3_value args[]){
sqlite3_result_java_object(cx, testResult.value);
}
};
int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func);
assert(0 == rc);
sqlite3_stmt stmt = new sqlite3_stmt();
sqlite3_prepare(db, "select myfunc()", stmt);
assert( 0 != stmt.getNativePointer() );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
sqlite3_value v = sqlite3_column_value(stmt, 0);
assert( testResult.value == sqlite3_value_java_object(v) );
assert( testResult.value == sqlite3_value_java_casted(v, Long.class) );
assert( testResult.value ==
sqlite3_value_java_casted(v, testResult.value.getClass()) );
assert( null == sqlite3_value_java_casted(v, Double.class) );
++n;
}
sqlite3_finalize(stmt);
assert( 1 == n );
sqlite3_close(db);
}
private static void testUdfAggregate(){
final sqlite3 db = createNewDb();
SQLFunction func = new SQLFunction.Aggregate(){
private int accum = 0;
@Override public void xStep(sqlite3_context cx, sqlite3_value args[]){
this.accum += sqlite3_value_int(args[0]);
}
@Override public void xFinal(sqlite3_context cx){
sqlite3_result_int(cx, this.accum);
this.accum = 0;
}
};
execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)");
int rc = sqlite3_create_function(db, "myfunc", 1, SQLITE_UTF8, func);
assert(0 == rc);
sqlite3_stmt stmt = new sqlite3_stmt();
sqlite3_prepare(db, "select myfunc(a) from t", stmt);
assert( 0 != stmt.getNativePointer() );
int n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
final int v = sqlite3_column_int(stmt, 0);
myassert( 6 == v );
++n;
}
sqlite3_reset(stmt);
// Ensure that the accumulator is reset...
n = 0;
if( SQLITE_ROW == sqlite3_step(stmt) ){
final int v = sqlite3_column_int(stmt, 0);
myassert( 6 == v );
++n;
}
sqlite3_finalize(stmt);
assert( 1==n );
sqlite3_close(db);
}
private static void testUdfWindow(){
final sqlite3 db = createNewDb();
/* Example window function, table, and results taken from:
https://sqlite.org/windowfunctions.html#udfwinfunc */
final SQLFunction func = new SQLFunction.Window(){
private int accum = 0;
private void xStepInverse(int v){
this.accum += v;
}
private void xFinalValue(sqlite3_context cx){
sqlite3_result_int(cx, this.accum);
}
@Override public void xStep(sqlite3_context cx, sqlite3_value[] args){
this.xStepInverse(sqlite3_value_int(args[0]));
}
@Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){
this.xStepInverse(-sqlite3_value_int(args[0]));
}
@Override public void xFinal(sqlite3_context cx){
this.xFinalValue(cx);
this.accum = 0;
}
@Override public void xValue(sqlite3_context cx){
this.xFinalValue(cx);
}
};
int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func);
myassert( 0 == rc );
execSql(db, new String[] {
"CREATE TEMP TABLE twin(x, y); INSERT INTO twin VALUES",
"('a', 4),('b', 5),('c', 3),('d', 8),('e', 1)"
});
sqlite3_stmt stmt = new sqlite3_stmt();
rc = sqlite3_prepare(db,
"SELECT x, winsumint(y) OVER ("+
"ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+
") AS sum_y "+
"FROM twin ORDER BY x;", stmt);
myassert( 0 == rc );
int n = 0;
while( SQLITE_ROW == sqlite3_step(stmt) ){
final String s = sqlite3_column_text(stmt, 0);
final int i = sqlite3_column_int(stmt, 1);
switch(++n){
case 1: myassert( "a".equals(s) && 9==i ); break;
case 2: myassert( "b".equals(s) && 12==i ); break;
case 3: myassert( "c".equals(s) && 16==i ); break;
case 4: myassert( "d".equals(s) && 12==i ); break;
case 5: myassert( "e".equals(s) && 9==i ); break;
default: myassert( false /* cannot happen */ );
}
}
sqlite3_finalize(stmt);
myassert( 5 == n );
sqlite3_close(db);
}
private static void listBoundMethods(){
//public static List<Field> getStatics(Class<?> clazz) {
if(false){
final java.lang.reflect.Field[] declaredFields =
SQLite3Jni.class.getDeclaredFields();
outln("Bound constants:\n");
for(java.lang.reflect.Field field : declaredFields) {
if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
outln("\t"+field.getName());
}
}
}
final java.lang.reflect.Method[] declaredMethods =
SQLite3Jni.class.getDeclaredMethods();
final java.util.List<String> funcList = new java.util.ArrayList<>();
for(java.lang.reflect.Method m : declaredMethods){
if((m.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0){
final String name = m.getName();
if(name.startsWith("sqlite3_")){
funcList.add(name);
}
}
}
int count = 0;
java.util.Collections.sort(funcList);
for(String n : funcList){
++count;
outln("\t"+n+"()");
}
outln(count+" functions named sqlite3_*.");
}
private static void testTrace(){
final sqlite3 db = createNewDb();
final ValueHolder<Integer> counter = new ValueHolder<>(0);
sqlite3_trace_v2(
db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE
| SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE,
new Tracer(){
public int xCallback(int traceFlag, long pNative, Object x){
++counter.value;
//outln("Trace #"+counter.value+" flag="+traceFlag+": "+x);
switch(traceFlag){
case SQLITE_TRACE_STMT:
// pNative ==> sqlite3_stmt
myassert(x instanceof String); break;
case SQLITE_TRACE_PROFILE:
// pNative ==> sqlite3_stmt
myassert(x instanceof Long); break;
case SQLITE_TRACE_ROW:
// pNative ==> sqlite3_stmt
case SQLITE_TRACE_CLOSE:
// pNative ==> sqlite3
myassert(null == x);
}
return 0;
}
});
execSql(db, "SELECT 1; SELECT 2");
myassert( 6 == counter.value );
sqlite3_close(db);
myassert( 7 == counter.value );
}
private static void testMisc(){
outln("Sleeping...");
sqlite3_sleep(500);
outln("Woke up.");
}
public static void main(String[] args){
test1();
if(false) testCompileOption();
final java.util.List<String> liArgs =
java.util.Arrays.asList(args);
testOpenDb1();
testOpenDb2();
testPrepare123();
testBindFetchInt();
testBindFetchInt64();
testBindFetchDouble();
testBindFetchText();
testBindFetchBlob();
testCollation();
testToUtf8();
testUdf1();
testUdfJavaObject();
testUdfAggregate();
testUdfWindow();
testTrace();
testMisc();
if(liArgs.indexOf("-v")>0){
listBoundMethods();
}
outln("Tests done. "+assertCount+" assertion(s) checked.");
}
}

View File

@ -0,0 +1,58 @@
/*
** 2023-07-22
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
Callback proxy for use with sqlite3_trace_v2().
*/
public interface Tracer {
/**
Called by sqlite3 for various tracing operations, as per
sqlite3_trace_v2(). Note that this interface elides the 2nd
argument to the native trace callback, as that role is better
filled by instance-local state.
The 2nd argument to this function, if non-0, will be a native
pointer to either an sqlite3 or sqlite3_stmt object, depending on
the first argument (see below). Client code can pass it to the
sqlite3 resp. sqlite3_stmt constructor to create a wrapping
object, if necessary. This API does not do so by default because
tracing can be called frequently, creating such a wrapper for
each call is comparatively expensive, and the objects are
probably only seldom useful.
The final argument to this function is the "X" argument
documented for sqlite3_trace() and sqlite3_trace_v2(). Its type
depends on value of the first argument:
- SQLITE_TRACE_STMT: pNative is a sqlite3_stmt. pX is a string
containing the prepared SQL, with one caveat/FIXME: JNI only
provides us with the ability to convert that string to MUTF-8,
as opposed to standard UTF-8, and is cannot be ruled out that
that difference may be significant for certain inputs. The
alternative would be that we first convert it to UTF-16 before
passing it on, but there's no readily-available way to do that
without calling back into the db to peform the conversion
(which would lead to further tracing).
- SQLITE_TRACE_PROFILE: pNative is a sqlite3_stmt. pX is a Long
holding an approximate number of nanoseconds the statement took
to run.
- SQLITE_TRACE_ROW: pNative is a sqlite3_stmt. pX is null.
- SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null.
*/
int xCallback(int traceFlag, long pNative, Object pX);
}

View File

@ -0,0 +1,25 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A helper class which simply holds a single value. Its current use
is for communicating values out of anonymous classes, as doing so
requires a "final" reference.
*/
public class ValueHolder<T> {
public T value;
public ValueHolder(){}
public ValueHolder(T v){value = v;}
}

View File

@ -0,0 +1,35 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A wrapper for communicating C-level (sqlite3*) instances with
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java
and C via JNI.
*/
public class sqlite3 extends NativePointerHolder<sqlite3> {
public sqlite3() {
super();
}
/**
Construct a new instance which refers to an existing
native (sqlite3*). The argument may be 0. Results are
undefined if it is not 0 and refers to a memory address
other than a valid (sqlite*).
*/
public sqlite3(long nativePointer) {
super(nativePointer);
}
}

View File

@ -0,0 +1,20 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
public class sqlite3_context extends NativePointerHolder<sqlite3_context> {
public sqlite3_context() {
super();
}
}

View File

@ -0,0 +1,35 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
/**
A wrapper for communicating C-level (sqlite3_stmt*) instances with
Java. These wrappers do not own their associated pointer, they
simply provide a type-safe way to communicate it between Java and C
via JNI.
*/
public class sqlite3_stmt extends NativePointerHolder<sqlite3_stmt> {
public sqlite3_stmt() {
super();
}
/**
Construct a new instance which refers to an existing native
(sqlite3_stmt*). The argument may be 0. Results are undefined if
it is not 0 and refers to a memory address other than a valid
(sqlite_stmt*).
*/
public sqlite3_stmt(long nativePointer) {
super(nativePointer);
}
}

View File

@ -0,0 +1,20 @@
/*
** 2023-07-21
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** This file is part of the JNI bindings for the sqlite3 C API.
*/
package org.sqlite.jni;
public class sqlite3_value extends NativePointerHolder<sqlite3_value> {
public sqlite3_value() {
super();
}
}

View File

@ -1,5 +1,5 @@
C Add\sthe\scontentless_delete=1\soption\sto\sfts5.\sFor\screating\scontentless\stables\sthat\ssupport\sDELETE\sand\sREPLACE\sstatements.
D 2023-07-27T19:13:35.663
C Initial\scheck-in\sof\sJNI\s(Java\sNative\sInterface)\sbindings\sfor\sthe\score\sC\sAPI.
D 2023-07-27T20:02:49.521
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -230,6 +230,23 @@ F ext/fts5/tool/showfts5.tcl d54da0e067306663e2d5d523965ca487698e722c
F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f400fc9
F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282
F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8
F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d
F ext/jni/README.md 5ce36c6f64208a2d8e7641e7ac255400a99f378f726fa44943a008bcb403aeb0
F ext/jni/src/c/sqlite3-jni.c 55bf5624beee849b1c063bf929e6066dc95437564c3212d30e672280bec45da8
F ext/jni/src/c/sqlite3-jni.h ef862321bb153135472ebe6be6df9db3e47448ae3ef6bb3cb7953c54971efcf8
F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1
F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 70dc7bc41f80352ff3d4331e2e24f45fcd23353b3641e2f68a81bd8262215861
F ext/jni/src/org/sqlite/jni/OutputPointer.java 08a752b58a33696c5eaf0eb9361a0966b188dec40f4a3613eb133123951f6c5f
F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5a1d7b2607eb2ef596fcf4492a49d1b3a5bdea3af9918e11716831ffd2f02284
F ext/jni/src/org/sqlite/jni/SQLFunction.java 2f5d197f6c7d73b6031ba1a19598d7e3eee5ebad467eeee62c72e585bd6556a5
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java d588c88c17290f5b0d1e4e2a1ea68cf9acab40891c98e08203f1b90ac2aaf8dd
F ext/jni/src/org/sqlite/jni/Tester1.java 512e545357ce1a5788b250395f2b198ae862f915aee1a8b7b8fae4620d0cfc8d
F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1
F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee
F ext/jni/src/org/sqlite/jni/sqlite3.java c7d0500c7269882243aafb41425928d094b2fcbdbc2fd1caffc276871cd3fae3
F ext/jni/src/org/sqlite/jni/sqlite3_context.java d781c72237e4a442adf6726b2edf15124405c28eba0387a279078858700f567c
F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 3193693440071998a66870544d1d2314f144bea397ce4c3f83ff225d587067a0
F ext/jni/src/org/sqlite/jni/sqlite3_value.java f9d8c0766b1d1b290564cb35db8d37be54c42adc8df22ee77b8d39e3e93398cd
F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9
F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013
F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86
@ -2049,9 +2066,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P fd59226b34fffb1479fb2d7bd7c0aff982aa4a1a73e6c0d81de6eaf9c075998c 719973d7f5a47b110e9919fcb96d21feab1e41356dbb3ec674c1116c17bbb778
R aea8a90a4ab6b65ce2d69f610973d78d
T +closed 719973d7f5a47b110e9919fcb96d21feab1e41356dbb3ec674c1116c17bbb778
U dan
Z dc969c62dd2115415f4e48c1e3d95206
P d66b182d2bc6ce0772e69401b7affe1adbc1b128c4631cb3c17f98dde72af00a
R 9595d1bd6387113a14191c3a678b4869
T *branch * jni
T *sym-jni *
T -sym-trunk * Cancelled\sby\sbranch.
U stephan
Z 1ff9901b09f119a40a482027609a6cc2
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
d66b182d2bc6ce0772e69401b7affe1adbc1b128c4631cb3c17f98dde72af00a
b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5