Initial check-in of JNI (Java Native Interface) bindings for the core C API.
FossilOrigin-Name: b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5
This commit is contained in:
parent
9cf8961557
commit
dff3443939
168
ext/jni/GNUmakefile
Normal file
168
ext/jni/GNUmakefile
Normal 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
213
ext/jni/README.md
Normal 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
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
1561
ext/jni/src/c/sqlite3-jni.h
Normal file
File diff suppressed because it is too large
Load Diff
28
ext/jni/src/org/sqlite/jni/Collation.java
Normal file
28
ext/jni/src/org/sqlite/jni/Collation.java
Normal 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() {}
|
||||
}
|
40
ext/jni/src/org/sqlite/jni/NativePointerHolder.java
Normal file
40
ext/jni/src/org/sqlite/jni/NativePointerHolder.java
Normal 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; }
|
||||
}
|
36
ext/jni/src/org/sqlite/jni/OutputPointer.java
Normal file
36
ext/jni/src/org/sqlite/jni/OutputPointer.java
Normal 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;}
|
||||
}
|
||||
}
|
23
ext/jni/src/org/sqlite/jni/ProgressHandler.java
Normal file
23
ext/jni/src/org/sqlite/jni/ProgressHandler.java
Normal 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();
|
||||
}
|
50
ext/jni/src/org/sqlite/jni/SQLFunction.java
Normal file
50
ext/jni/src/org/sqlite/jni/SQLFunction.java
Normal 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() {}
|
||||
}
|
||||
}
|
1202
ext/jni/src/org/sqlite/jni/SQLite3Jni.java
Normal file
1202
ext/jni/src/org/sqlite/jni/SQLite3Jni.java
Normal file
File diff suppressed because it is too large
Load Diff
675
ext/jni/src/org/sqlite/jni/Tester1.java
Normal file
675
ext/jni/src/org/sqlite/jni/Tester1.java
Normal 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.");
|
||||
}
|
||||
}
|
58
ext/jni/src/org/sqlite/jni/Tracer.java
Normal file
58
ext/jni/src/org/sqlite/jni/Tracer.java
Normal 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);
|
||||
}
|
25
ext/jni/src/org/sqlite/jni/ValueHolder.java
Normal file
25
ext/jni/src/org/sqlite/jni/ValueHolder.java
Normal 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;}
|
||||
}
|
35
ext/jni/src/org/sqlite/jni/sqlite3.java
Normal file
35
ext/jni/src/org/sqlite/jni/sqlite3.java
Normal 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);
|
||||
}
|
||||
}
|
20
ext/jni/src/org/sqlite/jni/sqlite3_context.java
Normal file
20
ext/jni/src/org/sqlite/jni/sqlite3_context.java
Normal 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();
|
||||
}
|
||||
}
|
35
ext/jni/src/org/sqlite/jni/sqlite3_stmt.java
Normal file
35
ext/jni/src/org/sqlite/jni/sqlite3_stmt.java
Normal 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);
|
||||
}
|
||||
}
|
20
ext/jni/src/org/sqlite/jni/sqlite3_value.java
Normal file
20
ext/jni/src/org/sqlite/jni/sqlite3_value.java
Normal 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();
|
||||
}
|
||||
}
|
33
manifest
33
manifest
@ -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.
|
||||
|
@ -1 +1 @@
|
||||
d66b182d2bc6ce0772e69401b7affe1adbc1b128c4631cb3c17f98dde72af00a
|
||||
b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5
|
Loading…
Reference in New Issue
Block a user