From dff3443939eb27633075a1a37e0738eef284476b Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 27 Jul 2023 20:02:49 +0000 Subject: [PATCH 001/148] Initial check-in of JNI (Java Native Interface) bindings for the core C API. FossilOrigin-Name: b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5 --- ext/jni/GNUmakefile | 168 ++ ext/jni/README.md | 213 ++ ext/jni/src/c/sqlite3-jni.c | 1935 +++++++++++++++++ ext/jni/src/c/sqlite3-jni.h | 1561 +++++++++++++ ext/jni/src/org/sqlite/jni/Collation.java | 28 + .../org/sqlite/jni/NativePointerHolder.java | 40 + ext/jni/src/org/sqlite/jni/OutputPointer.java | 36 + .../src/org/sqlite/jni/ProgressHandler.java | 23 + ext/jni/src/org/sqlite/jni/SQLFunction.java | 50 + ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 1202 ++++++++++ ext/jni/src/org/sqlite/jni/Tester1.java | 675 ++++++ ext/jni/src/org/sqlite/jni/Tracer.java | 58 + ext/jni/src/org/sqlite/jni/ValueHolder.java | 25 + ext/jni/src/org/sqlite/jni/sqlite3.java | 35 + .../src/org/sqlite/jni/sqlite3_context.java | 20 + ext/jni/src/org/sqlite/jni/sqlite3_stmt.java | 35 + ext/jni/src/org/sqlite/jni/sqlite3_value.java | 20 + manifest | 33 +- manifest.uuid | 2 +- 19 files changed, 6151 insertions(+), 8 deletions(-) create mode 100644 ext/jni/GNUmakefile create mode 100644 ext/jni/README.md create mode 100644 ext/jni/src/c/sqlite3-jni.c create mode 100644 ext/jni/src/c/sqlite3-jni.h create mode 100644 ext/jni/src/org/sqlite/jni/Collation.java create mode 100644 ext/jni/src/org/sqlite/jni/NativePointerHolder.java create mode 100644 ext/jni/src/org/sqlite/jni/OutputPointer.java create mode 100644 ext/jni/src/org/sqlite/jni/ProgressHandler.java create mode 100644 ext/jni/src/org/sqlite/jni/SQLFunction.java create mode 100644 ext/jni/src/org/sqlite/jni/SQLite3Jni.java create mode 100644 ext/jni/src/org/sqlite/jni/Tester1.java create mode 100644 ext/jni/src/org/sqlite/jni/Tracer.java create mode 100644 ext/jni/src/org/sqlite/jni/ValueHolder.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_context.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_stmt.java create mode 100644 ext/jni/src/org/sqlite/jni/sqlite3_value.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile new file mode 100644 index 0000000000..31a5e85036 --- /dev/null +++ b/ext/jni/GNUmakefile @@ -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) diff --git a/ext/jni/README.md b/ext/jni/README.md new file mode 100644 index 0000000000..0adf38527d --- /dev/null +++ b/ext/jni/README.md @@ -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 +``` + + +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 diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c new file mode 100644 index 0000000000..ba892afc56 --- /dev/null +++ b/ext/jni/src/c/sqlite3-jni.c @@ -0,0 +1,1935 @@ +/* +** 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 implements the JNI bindings declared in +** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). +*/ + +/* +** Define any SQLITE_... config defaults we want if they aren't +** overridden by the builder. Please keep these alphabetized. +*/ + +/**********************************************************************/ +/* SQLITE_D... */ +#ifndef SQLITE_DEFAULT_CACHE_SIZE +# define SQLITE_DEFAULT_CACHE_SIZE -16384 +#endif +#if !defined(SQLITE_DEFAULT_PAGE_SIZE) +# define SQLITE_DEFAULT_PAGE_SIZE 8192 +#endif +#ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix" +#endif +#undef SQLITE_DQS +#define SQLITE_DQS 0 + +/**********************************************************************/ +/* SQLITE_ENABLE_... */ +#ifndef SQLITE_ENABLE_BYTECODE_VTAB +# define SQLITE_ENABLE_BYTECODE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBPAGE_VTAB +# define SQLITE_ENABLE_DBPAGE_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_DBSTAT_VTAB +# define SQLITE_ENABLE_DBSTAT_VTAB 1 +#endif +#ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS +# define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 +#endif +#ifndef SQLITE_ENABLE_FTS4 +# define SQLITE_ENABLE_FTS4 1 +#endif +#ifndef SQLITE_ENABLE_MATH_FUNCTIONS +# define SQLITE_ENABLE_MATH_FUNCTIONS 1 +#endif +#ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC +# define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 +#endif +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ +#endif +#ifndef SQLITE_ENABLE_RTREE +# define SQLITE_ENABLE_RTREE 1 +#endif +#ifndef SQLITE_ENABLE_SESSION +# define SQLITE_ENABLE_SESSION 1 +#endif +#ifndef SQLITE_ENABLE_STMTVTAB +# define SQLITE_ENABLE_STMTVTAB 1 +#endif +#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +#endif + +/**********************************************************************/ +/* SQLITE_M... */ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 0x1fffffff +#endif + +/**********************************************************************/ +/* SQLITE_O... */ +#ifndef SQLITE_OMIT_DEPRECATED +# define SQLITE_OMIT_DEPRECATED 1 +#endif +#ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif +#ifndef SQLITE_OMIT_SHARED_CACHE +# define SQLITE_OMIT_SHARED_CACHE 1 +#endif +#ifdef SQLITE_OMIT_UTF16 +/* UTF16 is required for java */ +# undef SQLITE_OMIT_UTF16 1 +#endif + +/**********************************************************************/ +/* SQLITE_T... */ +#ifndef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 2 +#endif +#ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +#endif + +/**********************************************************************/ +/* SQLITE_USE_... */ +#ifndef SQLITE_USE_URI +# define SQLITE_USE_URI 1 +#endif + + +/* +** Which sqlite3.c we're using needs to be configurable to enable +** building against a custom copy, e.g. the SEE variant. We have to +** include sqlite3.c, as opposed to sqlite3.h, in order to get access +** to SQLITE_MAX_... and friends. This increases the rebuild time +** considerably but we need this in order to keep the exported values +** of SQLITE_MAX_... and SQLITE_LIMIT_... in sync with the C build. +*/ +#ifndef SQLITE_C +# define SQLITE_C sqlite3.c +#endif +#define INC__STRINGIFY_(f) #f +#define INC__STRINGIFY(f) INC__STRINGIFY_(f) +#include INC__STRINGIFY(SQLITE_C) +#undef INC__STRINGIFY_ +#undef INC__STRINGIFY +#undef SQLITE_C + +#include "sqlite3-jni.h" +#include /* only for testing/debugging */ +#include + +/* Only for debugging */ +#define MARKER(pfexp) \ + do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \ + printf pfexp; \ + } while(0) + +/* Creates a verbose JNI function name. */ +#define JFuncName(Suffix) \ + Java_org_sqlite_jni_SQLite3Jni_sqlite3_ ## Suffix + +/* Prologue for JNI functions. */ +#define JDECL(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncName(Suffix) +/* First 2 parameters to all JNI bindings. */ +#define JENV_JSELF JNIEnv * env, jobject jSelf +/* Helper to squelch -Xcheck:jni warnings about + not having checked for exceptions. */ +#define IFTHREW if((*env)->ExceptionCheck(env)) +#define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) +#define EXCEPTION_CLEAR (*env)->ExceptionClear(env) +#define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) +#define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT + + +#define PtrGet_sqlite3(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_context) +#define REF_G(VAR) (*env)->NewGlobalRef(env, VAR) +/*#define REF_L(VAR) (*env)->NewLocalRef(env, VAR)*/ +#define UNREF_G(VAR) if(VAR) (*env)->DeleteGlobalRef(env, (VAR)) +#define UNREF_L(VAR) if(VAR) (*env)->DeleteLocalRef(env, (VAR)) + +/** + Constant string class names used as keys for S3Global_nph_cache() and + friends. +*/ +static const struct { + const char * const sqlite3; + const char * const sqlite3_stmt; + const char * const sqlite3_context; + const char * const sqlite3_value; + const char * const OutputPointer_Int32; +} ClassNames = { + "org/sqlite/jni/sqlite3", + "org/sqlite/jni/sqlite3_stmt", + "org/sqlite/jni/sqlite3_context", + "org/sqlite/jni/sqlite3_value", + "org/sqlite/jni/OutputPointer$Int32" +}; + +/** Create a trivial JNI wrapper for (int CName(void)). */ +#define WRAP_INT_VOID(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JNIEnv *env, jobject jSelf){ \ + return (jint)CName(); \ + } + +#define WRAP_INT_INT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JNIEnv *env, jobject jSelf, jint arg){ \ + return (jint)CName((int)arg); \ + } + +/** Create a trivial JNI wrapper for (const mutf8_string * + CName(void)). This is only value for functions which are known to + return ASCII or text compatible with Modified UTF8. */ +#define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_JSELF){ \ + return (*env)->NewStringUTF( env, CName() ); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ +#define WRAP_INT_STMT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject jpStmt){ \ + jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ + EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ + return rc; \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ +#define WRAP_INT_DB(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ + return (jint)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ +#define WRAP_INT64_DB(JniNameSuffix,CName) \ + JDECL(jlong,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ + return (jlong)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ +#define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pStmt, jint n){ \ + return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ + } +/** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ +#define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ + JDECL(jstring,JniNameSuffix)(JENV_JSELF, jobject pStmt, jint ndx){ \ + return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ + } +/** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ +#define WRAP_INT_SVALUE(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject jpSValue){ \ + return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ + } + +/* Helpers for jstring and jbyteArray. */ +#define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) +#define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) +#define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) +#define JBA_RELEASE(ARG,VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) + +/* Marker for code which needs(?) to be made thread-safe. */ +#define FIXME_THREADING + +enum { + /** + Size of the per-JNIEnv cache. We have no way of knowing how many + distinct JNIEnv's will be used in any given run, but know that it + will normally be only 1. Perhaps (just speculating) differen + threads use separate JNIEnvs? If that's the case, we don't(?) + have enough info to evict from the cache when those JNIEnvs + expire. + + If this ever fills up, we can refactor this to dynamically + allocate them. + */ + JNIEnvCache_SIZE = 10, + /** + Need enough space for (only) the library's NativePointerHolder + types, a fixed count known at build-time. If we add more than this + a fatal error will be triggered with a reminder to increase this. + */ + NphCache_SIZE = 10 +}; + +/** + Cache for NativePointerHolder lookups. +*/ +typedef struct NphCacheLine NphCacheLine; +struct NphCacheLine { + const char * zClassName /* "full/class/Name" */; + jclass klazz /* global ref to concrete NPH class */; + jmethodID midSet /* setNativePointer() */; + jmethodID midGet /* getNativePointer() */; + jmethodID midCtor /* constructor */; +}; + +typedef struct JNIEnvCacheLine JNIEnvCacheLine; +struct JNIEnvCacheLine { + JNIEnv *env; + jclass globalClassObj /* global ref to java.lang.Object */; + jclass globalClassLong /* global ref to java.lang.Long */; + jmethodID ctorLong1 /* the Long(long) constructor */; + struct NphCacheLine nph[NphCache_SIZE]; +}; +typedef struct JNIEnvCache JNIEnvCache; +struct JNIEnvCache { + struct JNIEnvCacheLine lines[JNIEnvCache_SIZE]; + unsigned int used; +}; + +static void NphCacheLine_clear(JNIEnv *env, NphCacheLine * p){ + UNREF_G(p->klazz); + memset(p, 0, sizeof(NphCacheLine)); +} + +static void JNIEnvCacheLine_clear(JNIEnvCacheLine * p){ + JNIEnv *env = p->env; + int i; + if(env){ + UNREF_G(p->globalClassObj); + UNREF_G(p->globalClassLong); + i = 0; + for( ; i < NphCache_SIZE; ++i){ + NphCacheLine_clear(env, &p->nph[i]); + } + } +} + +static void JNIEnvCache_clear(JNIEnvCache * p){ + unsigned int i = 0; + for( ; i < p->used; ++i ){ + JNIEnvCacheLine_clear( &p->lines[i] ); + } + memset(p, 0, sizeof(JNIEnvCache)); +} + +/** + Per-(sqlite3*) state for bindings which do not have their own + finalizer functions, e.g. tracing and commit/rollback hooks. This + state is allocated as needed, cleaned up in sqlite3_close(_v2)(), + and recycled when possible. It is freed during sqlite3_shutdown(). + + Open questions: + + - Do we need to do a (JNIEnv*) for the db and each set of binding + data (since they can(?) hypothetically be set via multiple JNIEnv + objects)? +*/ +typedef struct PerDbState PerDbState; +struct PerDbState { + JNIEnv *env; + sqlite3 * pDb; + PerDbState * pNext; + PerDbState * pPrev; + struct { + jobject jObj; + jmethodID midCallback; + } trace; + struct { + jobject jObj; + jmethodID midCallback; + } progress; + struct { + jobject jObj; + jmethodID midCallback; + } commitHook; + struct { + jobject jObj; + jmethodID midCallback; + } rollbackHook; +}; + +static struct { + /** + According to: https://developer.ibm.com/articles/j-jni/ + + > A thread can get a JNIEnv by calling GetEnv() using the JNI + invocation interface through a JavaVM object. The JavaVM object + itself can be obtained by calling the JNI GetJavaVM() method + using a JNIEnv object and can be cached and shared across + threads. Caching a copy of the JavaVM object enables any thread + with access to the cached object to get access to its own + JNIEnv when necessary. + */ + JavaVM * jvm; + struct JNIEnvCache envCache; + struct { + PerDbState * aUsed; + PerDbState * aFree; + } perDb; +} S3Global; + +/** + sqlite3_malloc() proxy which fails fatally on OOM. This should + only be used for routines which manage global state and have no + recovery strategy for OOM. For sqlite3 API which can reasonably + return SQLITE_NOMEM, sqlite3_malloc() should be used instead. +*/ +static void * s3jni_malloc(JNIEnv *env, size_t n){ + void * rv = sqlite3_malloc(n); + if(n && !rv){ + (*env)->FatalError(env, "Out of memory.") /* does not return */; + } + return rv; +} + +static void s3jni_free(void * p){ + if(p) sqlite3_free(p); +} +/** + Extracts the new PerDbState instance from the free-list, or + allocates one if needed, associats it with pDb, and returns. + Returns NULL on OOM. +*/ +static PerDbState * PerDbState_alloc(JNIEnv *env, sqlite3 *pDb){ + PerDbState * rv; + assert( pDb ); + if(S3Global.perDb.aFree){ + rv = S3Global.perDb.aFree; + S3Global.perDb.aFree = rv->pNext; + if(rv->pNext){ + assert(rv->pNext->pPrev == rv); + assert(rv->pNext == rv->pNext->pPrev); + rv->pNext->pPrev = 0; + rv->pNext = 0; + } + }else{ + rv = s3jni_malloc(env, sizeof(PerDbState)); + if(rv){ + memset(rv, 0, sizeof(PerDbState)); + rv->pNext = S3Global.perDb.aUsed; + S3Global.perDb.aUsed = rv; + if(rv->pNext){ + assert(!rv->pNext->pPrev); + rv->pNext->pPrev = rv; + } + } + } + if(rv){ + rv->pDb = pDb; + rv->env = env; + } + return rv; +} + +/** + Clears s's state and moves it to the free-list. +*/ +FIXME_THREADING +static void PerDbState_set_aside(PerDbState *s){ + if(s){ + JNIEnv * const env = s->env; + assert(s->pDb && "Else this object is already in the free-list."); + if(s->pNext) s->pNext->pPrev = s->pPrev; + if(s->pPrev) s->pPrev->pNext = s->pNext; + else if(S3Global.perDb.aUsed == s){ + assert(!s->pPrev); + S3Global.perDb.aUsed = s->pNext; + } + UNREF_G(s->trace.jObj); + UNREF_G(s->progress.jObj); + UNREF_G(s->commitHook.jObj); + UNREF_G(s->rollbackHook.jObj); + s->env = 0; + s->pDb = 0; + s->pPrev = 0; + s->pNext = S3Global.perDb.aFree; + S3Global.perDb.aFree = s; + } +} + +/** + Returns the PerDbState object for the given db. If allocIfNeeded is + true then a new instance will be allocated if no mapping currently + exists, else NULL is returned if no mapping is found. + +*/ +FIXME_THREADING +static PerDbState * PerDbState_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeeded){ + PerDbState * s = S3Global.perDb.aUsed; + for( ; s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + if(allocIfNeeded) s = PerDbState_alloc(env, pDb); + return s; +} + +/** + Cleans up and frees all state in S3Global.perDb. +*/ +FIXME_THREADING +static void PerDbState_free_all(void){ + PerDbState * pS = S3Global.perDb.aUsed; + PerDbState * pSNext = 0; + for( ; pS; pS = pSNext ){ + pSNext = pS->pNext; + PerDbState_set_aside(pS); + assert(pSNext ? !pSNext->pPrev : 1); + } + assert( 0==S3Global.perDb.aUsed ); + pS = S3Global.perDb.aFree; + S3Global.perDb.aFree = 0; + pSNext = 0; + for( ; pS; pS = pSNext ){ + pSNext = pS->pNext; + s3jni_free(pSNext); + } +} + +/** + Fetches the S3Global.envCache row for the given env, allocing + a row if needed. When a row is allocated, its state is initialized + insofar as possible. Calls (*env)->FatalError() if the cache + fills up. That's hypothetically possible but "shouldn't happen." + If it does, we can dynamically allocate these instead. + +*/ +FIXME_THREADING +static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ + struct JNIEnvCacheLine * row = 0; + int i = 0; + for( ; i < JNIEnvCache_SIZE; ++i ){ + row = &S3Global.envCache.lines[i]; + if(row->env == env) return row; + else if(!row->env) break; + } + if(i == JNIEnvCache_SIZE){ + (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full."); + return NULL; + } + row->env = env; + row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + row->ctorLong1 = (*env)->GetMethodID(env, row->globalClassLong, + "", "(J)V"); + ++S3Global.envCache.used; + //MARKER(("Added S3Global.envCache entry #%d.\n", S3Global.envCache.used)); + return row; +} + +/** + Searches the NativePointerHolder cache for the given combination. + If it finds one, it returns it as-is. If it doesn't AND the cache + has a free slot, it populates that slot with (env, zClassName, + klazz) and returns it. If the cache is full with no match it + returns NULL. + + It is up to the caller to populate the other members of the returned + object if needed. + + zClassName must be a static string so we can use its address as a + cache key. + + This simple cache catches the overwhelming majority of searches + (>95%) in the current (2023-07-24) tests. +*/ +FIXME_THREADING +static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassName){ + /** + According to: + + https://developer.ibm.com/articles/j-jni/ + + > ... the IDs returned for a given class don't change for the + lifetime of the JVM process. But the call to get the field or + method can require significant work in the JVM, because + fields and methods might have been inherited from + superclasses, making the JVM walk up the class hierarchy to + find them. Because the IDs are the same for a given class, + you should look them up once and then reuse them. Similarly, + looking up class objects can be expensive, so they should be + cached as well. + */ + struct JNIEnvCacheLine * const envRow = S3Global_env_cache(env); + struct NphCacheLine * freeSlot = 0; + struct NphCacheLine * cacheLine = 0; + int i; + assert(envRow); + for( i = 0; i < NphCache_SIZE; ++i ){ + cacheLine = &envRow->nph[i]; + if(zClassName == cacheLine->zClassName){ +#if 0 + static unsigned int n = 0; + MARKER(("Cache hit #%u %s klazz@%p getter@%p, setter@%p, ctor@%p\n", + ++n, zClassName, cacheLine->klazz, cacheLine->midGet, + cacheLine->midSet, cacheLine->midCtor)); +#endif + assert(cacheLine->klazz); + return cacheLine; + }else if(!freeSlot && !cacheLine->zClassName){ + freeSlot = cacheLine; + } + } + if(freeSlot){ + freeSlot->zClassName = zClassName; + freeSlot->klazz = REF_G((*env)->FindClass(env, zClassName)); +#if 0 + static unsigned int cacheMisses = 0; + MARKER(("Cache miss #%u %s klazz@%p getter@%p, setter@%p, ctor@%p\n", + ++cacheMisses, zClassName, freeSlot->klazz, + freeSlot->midGet, freeSlot->midSet, freeSlot->midCtor)); +#endif + }else{ + (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); + } + return freeSlot; +} + +/** + Sets a native ptr value in NativePointerHolder object ppOut. + zClassName must be a static string so we can use its address + as a cache key. +*/ +static void setNativePointer(JNIEnv * env, jobject ppOut, void * p, + const char *zClassName){ + jmethodID setter = 0; + struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); + if(cacheLine && cacheLine->klazz && cacheLine->midSet){ + assert(zClassName == cacheLine->zClassName); + setter = cacheLine->midSet; + assert(setter); + }else{ + jclass const klazz = + cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, ppOut); + setter = (*env)->GetMethodID(env, klazz, "setNativePointer", "(J)V"); + if(cacheLine){ + assert(cacheLine->klazz); + assert(!cacheLine->midSet); + assert(zClassName == cacheLine->zClassName); + cacheLine->midSet = setter; + } + } + (*env)->CallVoidMethod(env, ppOut, setter, (jlong)p); + IFTHREW_REPORT; +} + +/** + Fetches a native ptr value from NativePointerHolder object ppOut. + zClassName must be a static string so we can use its address as a + cache key. +*/ +static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassName){ + if( 0==pObj ) return 0; + else{ + jmethodID getter = 0; + void * rv = 0; + struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); + if(cacheLine && cacheLine->midGet){ + getter = cacheLine->midGet; + }else{ + jclass const klazz = + cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, pObj); + getter = (*env)->GetMethodID(env, klazz, "getNativePointer", "()J"); + if(cacheLine){ + assert(cacheLine->klazz); + assert(zClassName == cacheLine->zClassName); + cacheLine->midGet = getter; + } + } + rv = (void*)(*env)->CallLongMethod(env, pObj, getter); + IFTHREW_REPORT; + return rv; + } +} + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own Java/JNI bindings. +** +** For purposes of certain hand-crafted JNI function bindings, we +** need a way of reporting errors which is consistent with the rest of +** the C API, as opposed to throwing JS exceptions. To that end, this +** internal-use-only function is a thin proxy around +** sqlite3ErrorWithMessage(). The intent is that it only be used from +** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not +** from client code. +** +** Returns err_code. +*/ +static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ + if( db!=0 ){ + if( 0!=zMsg ){ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + }else{ + sqlite3ErrorWithMsg(db, err_code, NULL); + } + } + return err_code; +} + + + +/* Sets a native int32 value in OutputPointer.Int32 object ppOut. */ +static void setOutputInt32(JNIEnv * env, jobject ppOut, int v){ + jmethodID setter = 0; + struct NphCacheLine * const cacheLine = + S3Global_nph_cache(env, ClassNames.OutputPointer_Int32); + if(cacheLine && cacheLine->klazz && cacheLine->midSet){ + setter = cacheLine->midSet; + }else{ + const jclass klazz = (*env)->GetObjectClass(env, ppOut); + setter = (*env)->GetMethodID(env, klazz, "setValue", "(I)V"); + if(cacheLine){ + assert(!cacheLine->midSet); + cacheLine->midSet = setter; + } + } + (*env)->CallVoidMethod(env, ppOut, setter, (jint)v); + IFTHREW_REPORT; +} + +#if 0 +/* Fetches a native int32 value from OutputPointer.Int32 object pObj. */ +static int getOutputInt(JNIEnv * env, jobject pObj){ + const jclass klazz = (*env)->GetObjectClass(env, pObj); + const jmethodID getter = + (*env)->GetMethodID(env, klazz, "getValue", "(V)I;"); + return (int)(*env)->CallIntMethod(env, pObj, getter); +} +#define VAL_GET_INT(OBJ) getOutputInt(env, OBJ) +#endif + +static int encodingTypeIsValid(int eTextRep){ + switch(eTextRep){ + case SQLITE_UTF8: case SQLITE_UTF16: + case SQLITE_UTF16LE: case SQLITE_UTF16BE: + return 1; + default: + return 0; + } +} + +/** + State for binding Java-side collation sequences. +*/ +typedef struct { + jclass klazz /* Collation object's class */; + jobject oCollation /* Collation instance */; + jmethodID midCompare /* cached xCompare */; + JNIEnv * env; /* env registered from */; +} CollationState; + +static CollationState * CollationState_alloc(void){ + CollationState * rc = sqlite3_malloc(sizeof(CollationState)); + if(rc) memset(rc, 0, sizeof(CollationState)); + return rc; +} + +static void CollationState_free(CollationState * cs){ + JNIEnv * const env = cs->env; + if(env){ + //MARKER(("Collation cleanup...\n")); + if(cs->oCollation) UNREF_G(cs->oCollation); + if(cs->klazz) UNREF_G(cs->klazz); + } + sqlite3_free(cs); +} + +static int collation_xCompare_proxy(void *pArg, int nLhs, const void *lhs, + int nRhs, const void *rhs){ + CollationState * const cs = (CollationState*)pArg; + JNIEnv * env = cs->env; + jint rc; + jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); + jbyteArray jbaRhs = (*env)->NewByteArray(env, (jint)nRhs); + //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); + (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); + (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); + rc = (*env)->CallIntMethod(env, cs->oCollation, cs->midCompare, + jbaLhs, jbaRhs); + EXCEPTION_IGNORE; + UNREF_L(jbaLhs); + UNREF_L(jbaRhs); + return (int)rc; +} + +static void collation_xDestroy_proxy(void *pArg){ + CollationState * const cs = (CollationState*)pArg; + if(cs->oCollation){ + JNIEnv * const env = cs->env; + const jmethodID method = (*env)->GetMethodID(env, cs->klazz, "xDestroy", + "()V"); + //MARKER(("Calling Collation.xDestroy()...\n")); + (*env)->CallVoidMethod(env, cs->oCollation, method); + IFTHREW { + MARKER(("Collation.xDestroy() threw. Ignoring!\n")); + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + } + //MARKER(("Returned from Collation.xDestroy().\n")); + } + CollationState_free(cs); +} + +/* State for sqlite3_result_java_object() and + sqlite3_value_java_object(). */ +typedef struct { + /* POTENTIAL bug: the JNI docs say that the JNIEnv pointer + is guaranteed to resolve the same for the same contexts, + but the docs are unclear as to whether it's the (JNIEnv *env) + or (*env) which resolves consistently. + + This posts claims it's unsave to cache JNIEnv at all, even when + it's always used in the same thread: + + https://stackoverflow.com/questions/12420463 + + And this one seems to contradict that: + + https://stackoverflow.com/questions/13964608 + + For later reference: + + https://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html#wp1242 + + https://developer.android.com/training/articles/perf-jni + + The later has the following say about caching: + + > If performance is important, it's useful to look the + [class/method ID] values up once and cache the results in your + native code. Because there is a limit of one JavaVM per + process, it's reasonable to store this data in a static local + structure. ... The class references, field IDs, and method IDs + are guaranteed valid until the class is unloaded. Classes are + only unloaded if all classes associated with a ClassLoader can + be garbage collected, which is rare but will not be impossible + in Android. Note however that the jclass is a class reference + and must be protected with a call to NewGlobalRef (see the next + section). + */ + JNIEnv * env; + jobject jObj; +} ResultJavaVal; + +/* For use with sqlite3_result/value_pointer() */ +#define RESULT_JAVA_VAL_STRING "ResultJavaVal" + +static ResultJavaVal * ResultJavaVal_alloc(JNIEnv *env, jobject jObj){ + ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); + if(rv){ + rv->env = env; + rv->jObj = jObj ? REF_G(jObj) : 0; + } + return rv; +} + +static void ResultJavaVal_finalizer(void *v){ + if(v){ + ResultJavaVal * const rv = (ResultJavaVal*)v; + if(rv->jObj) (*(rv->env))->DeleteGlobalRef(rv->env, rv->jObj); + sqlite3_free(rv); + } +} + + + +/** + Returns a new Java instance of the class named by zClassName, which + MUST be interface-compatible with NativePointerHolder and MUST have + a no-arg constructor. Its setNativePointer() method is passed + pNative. Hypothetically returns NULL if Java fails to allocate, but + the JNI docs are not entirely clear on that detail. + + Always use a string literal for the 2nd argument so that we can use + its address as a cache key. +*/ +static jobject new_NativePointerHolder_object(JNIEnv *env, const char *zClassName, + void * pNative){ + jobject rv = 0; + jclass klazz = 0; + jmethodID ctor = 0; + struct NphCacheLine * const cacheLine = + S3Global_nph_cache(env, zClassName); + if(cacheLine && cacheLine->midCtor){ + assert( cacheLine->klazz ); + klazz = cacheLine->klazz; + ctor = cacheLine->midCtor; + }else{ + klazz = cacheLine + ? cacheLine->klazz + : (*env)->FindClass(env, zClassName); + ctor = (*env)->GetMethodID(env, klazz, "", "()V"); + if(cacheLine){ + assert(zClassName == cacheLine->zClassName); + assert(cacheLine->klazz); + assert(!cacheLine->midCtor); + cacheLine->midCtor = ctor; + } + } + assert(klazz); + assert(ctor); + rv = (*env)->NewObject(env, klazz, ctor); + if(rv) setNativePointer(env, rv, pNative, zClassName); + return rv; +} + +static jobject new_sqlite3_value_wrapper(JNIEnv *env, sqlite3_value *sv){ + return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_value", sv); +} + +static jobject new_sqlite3_context_wrapper(JNIEnv *env, sqlite3_context *sv){ + return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_context", sv); +} + +static jobject new_sqlite3_wrapper(JNIEnv *env, sqlite3 *sv){ + return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3", sv); +} + +enum UDFType { + UDF_SCALAR = 1, + UDF_AGGREGATE, + UDF_WINDOW, + UDF_UNKNOWN_TYPE/*for error propagation*/ +}; + +typedef void (*udf_xFunc_f)(sqlite3_context*,int,sqlite3_value**); +typedef void (*udf_xStep_f)(sqlite3_context*,int,sqlite3_value**); +typedef void (*udf_xFinal_f)(sqlite3_context*); +/*typedef void (*udf_xValue_f)(sqlite3_context*);*/ +/*typedef void (*udf_xInverse_f)(sqlite3_context*,int,sqlite3_value**);*/ + +/** + State for binding Java-side UDFs. +*/ +typedef struct { + JNIEnv * env; /* env registered from */; + jobject jObj /* SQLFunction instance */; + jclass klazz /* jObj's class */; + char * zFuncName /* Only for error reporting and debug logging */; + enum UDFType type; + /** Method IDs for the various UDF methods. */ + jmethodID jmidxFunc; + jmethodID jmidxStep; + jmethodID jmidxFinal; + jmethodID jmidxValue; + jmethodID jmidxInverse; +} UDFState; + +static UDFState * UDFState_alloc(JNIEnv *env, jobject jObj){ + UDFState * const s = sqlite3_malloc(sizeof(UDFState)); + if(s){ + const char * zFSI = /* signature for xFunc, xStep, xInverse */ + "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; + const char * zFV = /* signature for xFinal, xValue */ + "(Lorg/sqlite/jni/sqlite3_context;)V"; + memset(s, 0, sizeof(UDFState)); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); +#define FGET(FuncName,FuncType,Field) \ + s->Field = (*env)->GetMethodID(env, s->klazz, FuncName, FuncType); \ + if(!s->Field) (*env)->ExceptionClear(env) + FGET("xFunc", zFSI, jmidxFunc); + FGET("xStep", zFSI, jmidxStep); + FGET("xFinal", zFV, jmidxFinal); + FGET("xValue", zFV, jmidxValue); + FGET("xInverse", zFSI, jmidxInverse); +#undef FGET + if(s->jmidxFunc) s->type = UDF_SCALAR; + else if(s->jmidxStep && s->jmidxFinal){ + s->type = s->jmidxValue ? UDF_WINDOW : UDF_AGGREGATE; + }else{ + s->type = UDF_UNKNOWN_TYPE; + } + } + return s; +} + +static void UDFState_free(UDFState * s){ + JNIEnv * const env = s->env; + if(env){ + //MARKER(("Collation cleanup...\n")); + if(s->jObj){ + const jmethodID method = + (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); + if(method){ + //MARKER(("aCalling SQLFunction.xDestroy()...\n")); + (*env)->CallVoidMethod(env, s->jObj, method); + EXCEPTION_IGNORE; + //MARKER(("Returned from SQLFunction.xDestroy().\n")); + }else{ + (*env)->ExceptionClear(env); + } + } + UNREF_G(s->jObj); + UNREF_G(s->klazz); + } + sqlite3_free(s->zFuncName); + sqlite3_free(s); +} + +static void UDFState_finalizer(void * s){ + if(s) UDFState_free((UDFState*)s); +} + +/** + Helper for processing args to UDF handlers + with signature (sqlite3_context*,int,sqlite3_value**). +*/ +typedef struct { + jobject jcx; + jobjectArray jargv; +} udf_jargs; + +static int udf_args(sqlite3_context * const cx, + int argc, sqlite3_value**argv, + UDFState * const s, + udf_jargs * const args){ + jobjectArray ja = 0; + JNIEnv * const env = s->env; + jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + jint i; + args->jcx = 0; + args->jargv = 0; + if(!jcx) goto error_oom; + ja = (*(s->env))->NewObjectArray(s->env, argc, + S3Global_env_cache(env)->globalClassObj, + NULL); + if(!ja) goto error_oom; + for(i = 0; i < argc; ++i){ + jobject jsv = new_sqlite3_value_wrapper(s->env, argv[i]); + if(!jsv) goto error_oom; + (*env)->SetObjectArrayElement(env, ja, i, jsv); + UNREF_L(jsv)/*array has a ref*/; + } + args->jcx = jcx; + args->jargv = ja; + return 0; +error_oom: + sqlite3_result_error_nomem(cx); + UNREF_L(jcx); + UNREF_L(ja); + return 1; +} + +static int udf_report_exception(sqlite3_context * cx, UDFState *s, + const char *zFuncType){ + int rc; + char * z = + sqlite3_mprintf("UDF %s.%s() threw. FIXME: extract " + "Java-side exception message.", + s->zFuncName, zFuncType); + if(z){ + sqlite3_result_error(cx, z, -1); + sqlite3_free(z); + rc = SQLITE_ERROR; + }else{ + rc = SQLITE_NOMEM; + } + return rc; +} + +static int udf_xFSI(sqlite3_context* cx, int argc, + sqlite3_value** argv, + UDFState * s, + jmethodID xMethodID, + const char * zFuncType){ + udf_jargs args; + JNIEnv * const env = s->env; + int rc = udf_args(cx, argc, argv, s, &args); + if(rc) return rc; + //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); + IFTHREW{ + rc = udf_report_exception(cx,s, zFuncType); + } + UNREF_L(args.jcx); + UNREF_L(args.jargv); + return rc; +} + +static int udf_xFV(sqlite3_context* cx, UDFState * s, + jmethodID xMethodID, + const char *zFuncType){ + JNIEnv * const env = s->env; + jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + int rc = 0; + if(!jcx){ + sqlite3_result_error_nomem(cx); + return SQLITE_NOMEM; + } + //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); + IFTHREW{ + rc = udf_report_exception(cx,s, zFuncType); + } + UNREF_L(jcx); + return rc; +} + +static void udf_xFunc(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + UDFState * const s = (UDFState*)sqlite3_user_data(cx); + udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); +} +static void udf_xStep(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + UDFState * const s = (UDFState*)sqlite3_user_data(cx); + udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); +} +static void udf_xFinal(sqlite3_context* cx){ + UDFState * const s = (UDFState*)sqlite3_user_data(cx); + udf_xFV(cx, s, s->jmidxFinal, "xFinal"); +} +static void udf_xValue(sqlite3_context* cx){ + UDFState * const s = (UDFState*)sqlite3_user_data(cx); + udf_xFV(cx, s, s->jmidxValue, "xValue"); +} +static void udf_xInverse(sqlite3_context* cx, int argc, + sqlite3_value** argv){ + UDFState * const s = (UDFState*)sqlite3_user_data(cx); + udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); +} + + +//////////////////////////////////////////////////////////////////////// +// What follows is the JNI/C bindings. They are in alphabetical order +// except for this macro-generated subset which are kept together here +// at the front... +//////////////////////////////////////////////////////////////////////// +WRAP_INT_DB(1errcode, sqlite3_errcode) +WRAP_INT_DB(1error_1offset, sqlite3_error_offset) +WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) +WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) +WRAP_INT_DB(1changes, sqlite3_changes) +WRAP_INT64_DB(1changes64, sqlite3_changes64) +WRAP_INT_STMT(1clear_1bindings, sqlite3_clear_bindings) +WRAP_INT_STMT_INT(1column_1bytes, sqlite3_column_bytes) +WRAP_INT_STMT_INT(1column_1bytes16, sqlite3_column_bytes16) +WRAP_INT_STMT(1column_1count, sqlite3_column_count) +WRAP_STR_STMT_INT(1column_1decltype, sqlite3_column_decltype) +WRAP_STR_STMT_INT(1column_1name, sqlite3_column_name) +WRAP_STR_STMT_INT(1column_1database_1name, sqlite3_column_database_name) +WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) +WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) +WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) +WRAP_INT_STMT(1data_1count, sqlite3_data_count) +WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) +WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) +WRAP_INT_STMT(1reset, sqlite3_reset) +WRAP_INT_INT(1sleep, sqlite3_sleep) +WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) +WRAP_INT_STMT(1step, sqlite3_step) +WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) +WRAP_INT_DB(1total_1changes, sqlite3_total_changes) +WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64) +WRAP_INT_SVALUE(1value_1bytes, sqlite3_value_bytes) +WRAP_INT_SVALUE(1value_1bytes16, sqlite3_value_bytes16) +WRAP_INT_SVALUE(1value_1encoding, sqlite3_value_encoding) +WRAP_INT_SVALUE(1value_1frombind, sqlite3_value_frombind) +WRAP_INT_SVALUE(1value_1nochange, sqlite3_value_nochange) +WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) +WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) +WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) + +JDECL(jint,1bind_1blob)(JENV_JSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + int rc; + if(!baData){ + rc = sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), ndx); + }else{ + jbyte * const pBuf = JBA_TOC(baData); + rc = sqlite3_bind_blob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, pBuf, (int)nMax, + SQLITE_TRANSIENT); + JBA_RELEASE(baData,pBuf); + } + return (jint)rc; +} + +JDECL(jint,1bind_1double)(JENV_JSELF, jobject jpStmt, + jint ndx, jdouble val){ + return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); +} + +JDECL(jint,1bind_1int)(JENV_JSELF, jobject jpStmt, + jint ndx, jint val){ + return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); +} + +JDECL(jint,1bind_1int64)(JENV_JSELF, jobject jpStmt, + jint ndx, jlong val){ + return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); +} + +JDECL(jint,1bind_1null)(JENV_JSELF, jobject jpStmt, + jint ndx){ + return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +JDECL(jint,1bind_1parameter_1index)(JENV_JSELF, jobject jpStmt, jbyteArray jName){ + int rc = 0; + jbyte * const pBuf = JBA_TOC(jName); + if(pBuf){ + rc = sqlite3_bind_parameter_index(PtrGet_sqlite3_stmt(jpStmt), + (const char *)pBuf); + JBA_RELEASE(jName, pBuf); + } + return rc; +} + +JDECL(jint,1bind_1text)(JENV_JSELF, jobject jpStmt, + jint ndx, jbyteArray baData, jint nMax){ + if(baData){ + jbyte * const pBuf = JBA_TOC(baData); + int rc = sqlite3_bind_text(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (const char *)pBuf, + (int)nMax, SQLITE_TRANSIENT); + JBA_RELEASE(baData, pBuf); + return (jint)rc; + }else{ + return sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + } +} + +JDECL(jint,1bind_1zeroblob)(JENV_JSELF, jobject jpStmt, + jint ndx, jint n){ + return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); +} + +JDECL(jint,1bind_1zeroblob64)(JENV_JSELF, jobject jpStmt, + jint ndx, jlong n){ + return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); +} + +JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject pDb, jint ms){ + return sqlite3_busy_timeout(PtrGet_sqlite3(pDb), (int)ms); +} + +/** + Wrapper for sqlite3_close(_v2)(). +*/ +static jint s3jni_close_db(JNIEnv *env, jobject jDb, int version){ + sqlite3 * pDb; + int rc = 0; + PerDbState * pS; + assert(version == 1 || version == 2); + pDb = PtrGet_sqlite3(jDb); + if(!pDb) return rc; + pS = PerDbState_for_db(env, pDb, 0); + rc = 1==version ? (jint)sqlite3_close(pDb) : (jint)sqlite3_close_v2(pDb); + if(pS) PerDbState_set_aside(pS) + /* MUST come after close() because of pS->trace. */; + setNativePointer(env, jDb, 0, ClassNames.sqlite3); + return (jint)rc; +} + +JDECL(jint,1close_1v2)(JENV_JSELF, jobject pDb){ + return s3jni_close_db(env, pDb, 2); +} + +JDECL(jint,1close)(JENV_JSELF, jobject pDb){ + return s3jni_close_db(env, pDb, 1); +} + +JDECL(jbyteArray,1column_1blob)(JENV_JSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + void const * const p = sqlite3_column_blob(pStmt, (int)ndx); + int const n = p ? sqlite3_column_bytes(pStmt, (int)ndx) : 0; + if( 0==p ) return NULL; + else{ + jbyteArray const jba = (*env)->NewByteArray(env, n); + (*env)->SetByteArrayRegion(env, jba, 0, n, (const jbyte *)p); + return jba; + } +} + +JDECL(jdouble,1column_1double)(JENV_JSELF, jobject jpStmt, + jint ndx){ + return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +JDECL(jint,1column_1int)(JENV_JSELF, jobject jpStmt, + jint ndx){ + return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +JDECL(jlong,1column_1int64)(JENV_JSELF, jobject jpStmt, + jint ndx){ + return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); +} + +/** + Expects to be passed a pointer from sqlite3_column_text16() or + sqlite3_value_text16() and a length value from + sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a + Java String of exactly half that length, returning NULL if !p or + (*env)->NewString() fails. +*/ +static jstring s3jni_text_to_jstring(JNIEnv *env, const void * const p, int nP){ + return p + ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) + : NULL; +} + +/** + Creates a new jByteArray of length nP, copies p's contents into it, and + returns that byte array. + */ +static jbyteArray s3jni_new_jbyteArray(JNIEnv *env, const unsigned char * const p, int nP){ + jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); + } + return jba; +} + +JDECL(jstring,1column_1text)(JENV_JSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes16(stmt, (int)ndx); + const void * const p = sqlite3_column_text16(stmt, (int)ndx); + return s3jni_text_to_jstring(env, p, n); +} + +JDECL(jbyteArray,1column_1text_1utf8)(JENV_JSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes(stmt, (int)ndx); + const unsigned char * const p = sqlite3_column_text(stmt, (int)ndx); + return s3jni_new_jbyteArray(env, p, n); +} + +JDECL(jobject,1column_1value)(JENV_JSELF, jobject jpStmt, + jint ndx){ + sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); + return new_sqlite3_value_wrapper(env, sv); +} + +JDECL(jstring,1compileoption_1get)(JENV_JSELF, jint n){ + return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); +} + +JDECL(jboolean,1compileoption_1used)(JENV_JSELF, jstring name){ + const char *zUtf8 = (*env)->GetStringUTFChars(env, name, NULL); + const jboolean rc = + 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; + (*env)->ReleaseStringUTFChars(env, name, zUtf8); + return rc; +} + +JDECL(jobject,1context_1db_1handle)(JENV_JSELF, jobject jpCx){ + sqlite3 * const db = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); + return db ? new_sqlite3_wrapper(env, db) : NULL; +} + +JDECL(jint,1create_1collation)(JENV_JSELF, jobject jpDb, + jstring name, jint eTextRep, + jobject oCollation){ + const jclass klazz = (*env)->GetObjectClass(env, oCollation); + int rc; + const char *zName; + CollationState * const cs = CollationState_alloc(); + if(!cs) return (jint)SQLITE_NOMEM; + cs->env = env; + cs->oCollation = REF_G(oCollation); + cs->klazz = REF_G(klazz); + cs->midCompare = (*env)->GetMethodID(env, klazz, "xCompare", + "([B[B)I"); + zName = JSTR_TOC(name); + rc = sqlite3_create_collation_v2(PtrGet_sqlite3(jpDb), zName, (int)eTextRep, + cs, collation_xCompare_proxy, + collation_xDestroy_proxy); + JSTR_RELEASE(name, zName); + if(0 != rc) collation_xDestroy_proxy(cs); + return (jint)rc; +} + +static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ + UDFState * s = 0; + int rc; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + const char * zFuncName = 0; + + if( !encodingTypeIsValid(eTextRep) ){ + return s3jni_db_error(pDb, SQLITE_FORMAT, + "Invalid function encoding option."); + } + s = UDFState_alloc(env, jFunctor); + if( !s ) return SQLITE_NOMEM; + else if( UDF_UNKNOWN_TYPE==s->type ){ + UDFState_free(s); + rc = s3jni_db_error(pDb, SQLITE_MISUSE, + "Cannot unambiguously determine function type."); + goto error_cleanup; + } + zFuncName = JSTR_TOC(jFuncName); + if(!zFuncName){ + UDFState_free(s); + rc = SQLITE_NOMEM; + goto error_cleanup; + } + if( UDF_WINDOW == s->type ){ + rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s, + udf_xStep, udf_xFinal, udf_xValue, + udf_xInverse, UDFState_finalizer); + }else{ + udf_xFunc_f xFunc = 0; + udf_xStep_f xStep = 0; + udf_xFinal_f xFinal = 0; + if( UDF_SCALAR == s->type ){ + xFunc = udf_xFunc; + }else{ + assert( UDF_AGGREGATE == s->type ); + xStep = udf_xStep; + xFinal = udf_xFinal; + } + rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, + xFunc, xStep, xFinal, + UDFState_finalizer); + } + s->zFuncName = sqlite3_mprintf("%s", zFuncName); + if(!s->zFuncName){ + rc = SQLITE_NOMEM; + UDFState_free(s); + } +error_cleanup: + JSTR_RELEASE(jFuncName, zFuncName); + /* on create_function() error, s will be destroyed via create_function() */ + return (jint)rc; +} + +JDECL(jint,1create_1function)(JENV_JSELF, jobject jDb, jstring jFuncName, + jint nArg, jint eTextRep, jobject jFunctor){ + return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); +} + +/* +JDECL(jint,1create_1window_1function)(JENV_JSELF, jstring jFuncName, jint nArg, + jint eTextRep, jobject jFunctor){ + return create_function_mega(env, jFuncName, nArg, eTextRep, jFunctor); +} +*/ + +JDECL(jstring,1errmsg)(JENV_JSELF, jobject jpDb){ + return (*env)->NewStringUTF(env, sqlite3_errmsg(PtrGet_sqlite3(jpDb))); +} + +JDECL(jstring,1errstr)(JENV_JSELF, jint rcCode){ + return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)); +} + +JDECL(jboolean,1extended_1result_1codes)(JENV_JSELF, jobject jpDb, + jboolean onoff){ + int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); + return rc ? JNI_TRUE : JNI_FALSE; +} + +JDECL(jint,1initialize)(JENV_JSELF){ + return sqlite3_initialize(); +} + +JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ + if(jpStmt){ + sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt); + setNativePointer(env, jpStmt, 0, ClassNames.sqlite3_stmt); + sqlite3_finalize(pStmt); + } + return 0; +} + + +JDECL(jlong,1last_1insert_1rowid)(JENV_JSELF, jobject jpDb){ + return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); +} + + +JDECL(jint,1open)(JENV_JSELF, jstring strName, jobject ppOut){ + sqlite3 * pOut = 0; + const char *zName = strName ? JSTR_TOC(strName) : 0; + int nrc = sqlite3_open(zName, &pOut); + //MARKER(("env=%p, *env=%p\n", env, *env)); + setNativePointer(env, ppOut, pOut, ClassNames.sqlite3); + assert(nrc==0 ? pOut!=0 : 1); + JSTR_RELEASE(strName, zName); + return (jint)nrc; +} + +JDECL(jint,1open_1v2)(JENV_JSELF, jstring strName, + jobject ppOut, jint flags, jstring strVfs){ + sqlite3 * pOut = 0; + const char *zName = strName ? JSTR_TOC(strName) : 0; + const char *zVfs = strVfs ? JSTR_TOC(strVfs) : 0; + int nrc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", + zName, zVfs, pOut, (int)flags, nrc));*/ + setNativePointer(env, ppOut, pOut, ClassNames.sqlite3); + assert(nrc==0 ? pOut!=0 : 1); + JSTR_RELEASE(strName, zName); + JSTR_RELEASE(strVfs, zVfs); + return (jint)nrc; +} + +/* Proxy for the sqlite3_prepare[_v2/3]() family. */ +static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv *env, jclass self, + jobject jpDb, jbyteArray baSql, + jint nMax, jint prepFlags, + jobject outStmt, jobject outTail){ + sqlite3_stmt * pStmt = 0; + const char * zTail = 0; + jbyte * const pBuf = JBA_TOC(baSql); + int rc = SQLITE_ERROR; + assert(prepVersion==1 || prepVersion==2 || prepVersion==3); + switch( prepVersion ){ + case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jpDb), (const char *)pBuf, + (int)nMax, &pStmt, &zTail); + break; + case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jpDb), (const char *)pBuf, + (int)nMax, &pStmt, &zTail); + break; + case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jpDb), (const char *)pBuf, + (int)nMax, (unsigned int)prepFlags, + &pStmt, &zTail); + break; + default: + assert(0 && "Invalid prepare() version"); + } + JBA_RELEASE(baSql,pBuf); + if( 0!=outTail ){ + assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); + assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); + setOutputInt32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); + } + setNativePointer(env, outStmt, pStmt, ClassNames.sqlite3_stmt); + return (jint)rc; +} +JDECL(jint,1prepare)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, + jint nMax, jobject outStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0, + outStmt, outTail); +} +JDECL(jint,1prepare_1v2)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, + jint nMax, jobject outStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0, + outStmt, outTail); +} +JDECL(jint,1prepare_1v3)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, + jint nMax, jint prepFlags, jobject outStmt, jobject outTail){ + return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax, + prepFlags, outStmt, outTail); +} + +/* sqlite3_result_text/blob() and friends. */ +static void result_blob_text(int asBlob, int as64, + int eTextRep/*only for (asBlob=0)*/, + JNIEnv *env, sqlite3_context *pCx, + jbyteArray jBa, jlong nMax){ + if(jBa){ + jbyte * const pBuf = JBA_TOC(jBa); + jsize nBa = (*env)->GetArrayLength(env, jBa); + if( nMax>=0 && nBa>(jsize)nMax ){ + nBa = (jsize)nMax; + /** + From the sqlite docs: + + > If the 3rd parameter to any of the sqlite3_result_text* + interfaces other than sqlite3_result_text64() is negative, + then SQLite computes the string length itself by searching + the 2nd parameter for the first zero character. + + Note that the text64() interfaces take an unsigned value for + the length, which Java does not support. This binding takes + the approach of passing on negative values to the C API, + which will, in turn fail with SQLITE_TOOBIG at some later + point (recall that the sqlite3_result_xyz() family do not + have result values). + */ + } + if(as64){ /* 64-bit... */ + static const jsize nLimit64 = + SQLITE_MAX_ALLOCATION_SIZE/*only _kinda_ arbitrary!*/ + /* jsize is int32, not int64! */; + if(nBa > nLimit64){ + sqlite3_result_error_toobig(pCx); + }else if(asBlob){ + sqlite3_result_blob64(pCx, pBuf, (sqlite3_uint64)nBa, + SQLITE_TRANSIENT); + }else{ /* text64... */ + if(encodingTypeIsValid(eTextRep)){ + sqlite3_result_text64(pCx, (const char *)pBuf, + (sqlite3_uint64)nBa, + SQLITE_TRANSIENT, eTextRep); + }else{ + sqlite3_result_error_code(pCx, SQLITE_FORMAT); + } + } + }else{ /* 32-bit... */ + static const jsize nLimit = SQLITE_MAX_ALLOCATION_SIZE; + if(nBa > nLimit){ + sqlite3_result_error_toobig(pCx); + }else if(asBlob){ + sqlite3_result_blob(pCx, pBuf, (int)nBa, + SQLITE_TRANSIENT); + }else{ + switch(eTextRep){ + case SQLITE_UTF8: + sqlite3_result_text(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16: + sqlite3_result_text16(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16LE: + sqlite3_result_text16le(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + case SQLITE_UTF16BE: + sqlite3_result_text16be(pCx, (const char *)pBuf, (int)nBa, + SQLITE_TRANSIENT); + break; + } + } + JBA_RELEASE(jBa, pBuf); + } + }else{ + sqlite3_result_null(pCx); + } +} + +JDECL(void,1result_1blob)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jint nMax){ + return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1blob64)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ + return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1double)(JENV_JSELF, jobject jpCx, jdouble v){ + sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); +} + +JDECL(void,1result_1error)(JENV_JSELF, jobject jpCx, jbyteArray baMsg, + int eTextRep){ + const char * zUnspecified = "Unspecified error."; + jsize const baLen = (*env)->GetArrayLength(env, baMsg); + jbyte * const pjBuf = baMsg ? JBA_TOC(baMsg) : NULL; + switch(pjBuf ? eTextRep : SQLITE_UTF8){ + case SQLITE_UTF8: { + const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified; + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, baLen); + break; + } + case SQLITE_UTF16: { + const void *zMsg = pjBuf + ? (const void *)pjBuf : (const void *)zUnspecified; + sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, baLen); + break; + } + default: + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), + "Invalid encoding argument passed " + "to sqlite3_result_error().", -1); + break; + } + JBA_RELEASE(baMsg,pjBuf); +} + +JDECL(void,1result_1error_1code)(JENV_JSELF, jobject jpCx, jint v){ + sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); +} + +JDECL(void,1result_1error_1nomem)(JENV_JSELF, jobject jpCx){ + sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1error_1toobig)(JENV_JSELF, jobject jpCx){ + sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1int)(JENV_JSELF, jobject jpCx, jint v){ + sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v); +} + +JDECL(void,1result_1int64)(JENV_JSELF, jobject jpCx, jlong v){ + sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +} + +JDECL(void,1result_1java_1object)(JENV_JSELF, jobject jpCx, jobject v){ + if(v){ + ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); + if(rjv){ + sqlite3_result_pointer(PtrGet_sqlite3_context(jpCx), rjv, RESULT_JAVA_VAL_STRING, + ResultJavaVal_finalizer); + }else{ + sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); + } + }else{ + sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); + } +} + +JDECL(void,1result_1null)(JENV_JSELF, jobject jpCx){ + sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); +} + +JDECL(void,1result_1text)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jint nMax){ + return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1text64)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jlong nMax, + jint eTextRep){ + return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); +} + +JDECL(void,1result_1value)(JENV_JSELF, jobject jpCx, jobject jpSVal){ + sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(void,1result_1zeroblob)(JENV_JSELF, jobject jpCx, jint v){ + sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v); +} + +JDECL(jint,1result_1zeroblob64)(JENV_JSELF, jobject jpCx, jlong v){ + return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); +} + + +JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ + sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), + (sqlite3_int64)rowId); +} + +JDECL(jint,1shutdown)(JENV_JSELF){ + PerDbState_free_all(); + JNIEnvCache_clear(&S3Global.envCache); + /* Do not clear S3Global.jvm: it's legal to call + sqlite3_initialize() again to restart the lib. */ + return sqlite3_shutdown(); +} + +static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ + PerDbState * const ps = (PerDbState *)pC; + JNIEnv * const env = ps->env; + jobject jX = NULL; + JNIEnvCacheLine * const pEcl = S3Global_env_cache(env); + int rc; + /** + TODO: convert pX depending on traceflag: + + SQLITE_TRACE_STMT: String + SQLITE_TRACE_PROFILE: Long + others: null + */ + switch(traceflag){ + case SQLITE_TRACE_STMT: + /* This is not _quite_ right: we're converting to MUTF-8. It + should(?) suffice for purposes of tracing, though. */ + jX = (*env)->NewStringUTF(env, (const char *)pX); + break; + case SQLITE_TRACE_PROFILE: + jX = (*env)->NewObject(env, pEcl->globalClassLong, pEcl->ctorLong1, + (jlong)*((sqlite3_int64*)pX)); + break; + } + rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, + ps->trace.midCallback, + (jint)traceflag, (jlong)pP, jX); + UNREF_L(jX); + IFTHREW{ + EXCEPTION_CLEAR; + return rc ? rc : + s3jni_db_error(ps->pDb, SQLITE_ERROR, + "sqlite3_trace_v2() callback threw."); + } + return rc; +} + +JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + PerDbState * ps; + jclass klazz; + if( !traceMask || !jTracer ){ + return (jint)sqlite3_trace_v2(pDb, 0, 0, 0); + } + ps = PerDbState_for_db(env, pDb, 1); + if(!ps) return SQLITE_NOMEM; + klazz = (*env)->GetObjectClass(env, jTracer); + ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + "(IJLjava/lang/Object;)I"); + IFTHREW { + /* Leave ps in place - it might contain other state. */ + EXCEPTION_CLEAR; + return s3jni_db_error(pDb, SQLITE_ERROR, + "Cannot not find matchin xCallback() on Tracer object."); + } + ps->trace.jObj = REF_G(jTracer); + return sqlite3_trace_v2(pDb, (unsigned)traceMask, s3jni_trace_impl, ps); +} + + +JDECL(jbyteArray,1value_1blob)(JENV_JSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const nLen = sqlite3_value_bytes(sv); + const jbyte * pBytes = sqlite3_value_blob(sv); + jbyteArray const jba = pBytes + ? (*env)->NewByteArray(env, (jsize)nLen) + : NULL; + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); + } + return jba; +} + + +JDECL(jdouble,1value_1double)(JENV_JSELF, jobject jpSVal){ + return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal)); +} + + +JDECL(jobject,1value_1dup)(JENV_JSELF, jobject jpSVal){ + sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal)); + return sv ? new_sqlite3_value_wrapper(env, sv) : 0; +} + +JDECL(void,1value_1free)(JENV_JSELF, jobject jpSVal){ + sqlite3_value_free(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jint,1value_1int)(JENV_JSELF, jobject jpSVal){ + return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jlong,1value_1int64)(JENV_JSELF, jobject jpSVal){ + return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal)); +} + +JDECL(jobject,1value_1java_1object)(JENV_JSELF, jobject jpSVal){ + ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); + return rv ? rv->jObj : NULL; +} + +JDECL(jstring,1value_1text)(JENV_JSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const n = sqlite3_value_bytes16(sv); + const void * const p = sqlite3_value_text16(sv); + return s3jni_text_to_jstring(env, p, n); +} + +JDECL(jbyteArray,1value_1text_1utf8)(JENV_JSELF, jobject jpSVal){ + sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); + int const n = sqlite3_value_bytes(sv); + const unsigned char * const p = sqlite3_value_text(sv); + return s3jni_new_jbyteArray(env, p, n); +} + +static jbyteArray value_text16(int mode, JNIEnv *env, jobject jpSVal){ + int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); + jbyteArray jba; + const jbyte * pBytes; + switch(mode){ + case SQLITE_UTF16: + pBytes = sqlite3_value_text16(PtrGet_sqlite3_value(jpSVal)); + break; + case SQLITE_UTF16LE: + pBytes = sqlite3_value_text16le(PtrGet_sqlite3_value(jpSVal)); + break; + case SQLITE_UTF16BE: + pBytes = sqlite3_value_text16be(PtrGet_sqlite3_value(jpSVal)); + break; + default: + assert(!"not possible"); + return NULL; + } + jba = pBytes + ? (*env)->NewByteArray(env, (jsize)nLen) + : NULL; + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, nLen, pBytes); + } + return jba; +} + +JDECL(jbyteArray,1value_1text16)(JENV_JSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16, env, jpSVal); +} + +JDECL(jbyteArray,1value_1text16le)(JENV_JSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16LE, env, jpSVal); +} + +JDECL(jbyteArray,1value_1text16be)(JENV_JSELF, jobject jpSVal){ + return value_text16(SQLITE_UTF16BE, env, jpSVal); +} + + + +//////////////////////////////////////////////////////////////////////// +// End of the main API bindings. What follows are internal utilities. +//////////////////////////////////////////////////////////////////////// + +/** + Called during static init of the SQLite3Jni class to sync certain + compile-time constants to Java-space. + + This routine is why we have to #include sqlite3.c instead of + sqlite3.h. +*/ +JNIEXPORT void JNICALL +Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv *env, jclass self, jobject sJni){ + typedef struct { + const char *zName; + int value; + } LimitEntry; + const LimitEntry aLimits[] = { + {"SQLITE_MAX_ALLOCATION_SIZE", SQLITE_MAX_ALLOCATION_SIZE}, + {"SQLITE_LIMIT_LENGTH", SQLITE_LIMIT_LENGTH}, + {"SQLITE_MAX_LENGTH", SQLITE_MAX_LENGTH}, + {"SQLITE_LIMIT_SQL_LENGTH", SQLITE_LIMIT_SQL_LENGTH}, + {"SQLITE_MAX_SQL_LENGTH", SQLITE_MAX_SQL_LENGTH}, + {"SQLITE_LIMIT_COLUMN", SQLITE_LIMIT_COLUMN}, + {"SQLITE_MAX_COLUMN", SQLITE_MAX_COLUMN}, + {"SQLITE_LIMIT_EXPR_DEPTH", SQLITE_LIMIT_EXPR_DEPTH}, + {"SQLITE_MAX_EXPR_DEPTH", SQLITE_MAX_EXPR_DEPTH}, + {"SQLITE_LIMIT_COMPOUND_SELECT", SQLITE_LIMIT_COMPOUND_SELECT}, + {"SQLITE_MAX_COMPOUND_SELECT", SQLITE_MAX_COMPOUND_SELECT}, + {"SQLITE_LIMIT_VDBE_OP", SQLITE_LIMIT_VDBE_OP}, + {"SQLITE_MAX_VDBE_OP", SQLITE_MAX_VDBE_OP}, + {"SQLITE_LIMIT_FUNCTION_ARG", SQLITE_LIMIT_FUNCTION_ARG}, + {"SQLITE_MAX_FUNCTION_ARG", SQLITE_MAX_FUNCTION_ARG}, + {"SQLITE_LIMIT_ATTACHED", SQLITE_LIMIT_ATTACHED}, + {"SQLITE_MAX_ATTACHED", SQLITE_MAX_ATTACHED}, + {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, + {"SQLITE_MAX_LIKE_PATTERN_LENGTH", SQLITE_MAX_LIKE_PATTERN_LENGTH}, + {"SQLITE_LIMIT_VARIABLE_NUMBER", SQLITE_LIMIT_VARIABLE_NUMBER}, + {"SQLITE_MAX_VARIABLE_NUMBER", SQLITE_MAX_VARIABLE_NUMBER}, + {"SQLITE_LIMIT_TRIGGER_DEPTH", SQLITE_LIMIT_TRIGGER_DEPTH}, + {"SQLITE_MAX_TRIGGER_DEPTH", SQLITE_MAX_TRIGGER_DEPTH}, + {"SQLITE_LIMIT_WORKER_THREADS", SQLITE_LIMIT_WORKER_THREADS}, + {"SQLITE_MAX_WORKER_THREADS", SQLITE_MAX_WORKER_THREADS}, + {0,0} + }; + jfieldID fieldId; + jclass const klazz = (*env)->GetObjectClass(env, sJni); + const LimitEntry * pLimit; + memset(&S3Global, 0, sizeof(S3Global)); + (void)S3Global_env_cache(env); + assert( 1 == S3Global.envCache.used ); + assert( env == S3Global.envCache.lines[0].env ); + assert( 0 != S3Global.envCache.lines[0].globalClassObj ); + if( (*env)->GetJavaVM(env, &S3Global.jvm) ){ + (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); + } + + for( pLimit = &aLimits[0]; pLimit->zName; ++pLimit ){ + fieldId = (*env)->GetStaticFieldID(env, klazz, pLimit->zName, "I"); + //MARKER(("Setting %s (field=%p) = %d\n", pLimit->zName, fieldId, pLimit->value)); + assert(fieldId); + (*env)->SetStaticIntField(env, klazz, fieldId, (jint)pLimit->value); + } +} diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h new file mode 100644 index 0000000000..a55d437152 --- /dev/null +++ b/ext/jni/src/c/sqlite3-jni.h @@ -0,0 +1,1561 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_SQLite3Jni */ + +#ifndef _Included_org_sqlite_jni_SQLite3Jni +#define _Included_org_sqlite_jni_SQLite3Jni +#ifdef __cplusplus +extern "C" { +#endif +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_EXISTS 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READWRITE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_ACCESS_READ 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DENY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DENY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IGNORE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_INDEX 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TABLE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_INDEX 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TABLE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_TRIGGER 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TEMP_VIEW 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_TRIGGER 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VIEW 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DELETE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DELETE 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_INDEX 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TABLE 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_INDEX 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TABLE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_TRIGGER 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TEMP_VIEW 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_TRIGGER 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VIEW 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INSERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INSERT 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_PRAGMA 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_READ 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SELECT +#define org_sqlite_jni_SQLite3Jni_SQLITE_SELECT 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSACTION 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UPDATE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH +#define org_sqlite_jni_SQLite3Jni_SQLITE_ATTACH 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETACH +#define org_sqlite_jni_SQLite3Jni_SQLITE_DETACH 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ALTER_TABLE 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_REINDEX 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_ANALYZE 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CREATE_VTABLE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DROP_VTABLE 30L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_FUNCTION 31L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_SAVEPOINT 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE 33L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_INVERT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_IGNORENOOP 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_DATA 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_NOTFOUND 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONFLICT 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_CONSTRAINT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_FOREIGN_KEY 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_OMIT 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_REPLACE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESET_ABORT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SINGLETHREAD 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MULTITHREAD 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SERIALIZED 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MALLOC 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMALLOC 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SCRATCH 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PAGECACHE 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_HEAP 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMSTATUS 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MUTEX 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETMUTEX 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOOKASIDE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_LOG 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_URI 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE2 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_GETPCACHE2 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_COVERING_INDEX_SCAN 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SQLLOG 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MMAP_SIZE 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_WIN32_HEAPSIZE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PCACHE_HDRSZ 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_PMASZ 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_STMTJRNL_SPILL 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SMALL_MALLOC 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_SORTERREF_SIZE 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONFIG_MEMDB_MAXSIZE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTEGER 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FLOAT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TEXT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TEXT 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BLOB +#define org_sqlite_jni_SQLite3Jni_SQLITE_BLOB 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_NULL 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAINDBNAME 1000L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LOOKASIDE 1001L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FKEY 1002L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_TRIGGER 1003L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_QPSG 1007L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRIGGER_EQP 1008L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_RESET_DATABASE 1009L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DEFENSIVE 1010L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DML 1013L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_DQS_DDL 1014L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_ENABLE_VIEW 1015L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_STMT_SCANSTATUS 1018L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_REVERSE_SCANORDER 1019L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBCONFIG_MAX 1019L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_USED 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_SCHEMA_USED 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_STMT_USED 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_HIT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_HIT 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_MISS 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_WRITE 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_DEFERRED_FKS 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_USED_SHARED 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_CACHE_SPILL 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX +#define org_sqlite_jni_SQLite3Jni_SQLITE_DBSTATUS_MAX 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF8 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16LE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16BE 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED +#define org_sqlite_jni_SQLite3Jni_SQLITE_UTF16_ALIGNED 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCKSTATE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_GET_LOCKPROXYFILE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SET_LOCKPROXYFILE 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LAST_ERRNO 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_HINT 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CHUNK_SIZE 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_FILE_POINTER 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC_OMITTED 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_AV_RETRY 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PERSIST_WAL 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_OVERWRITE 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFSNAME 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_POWERSAFE_OVERWRITE 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PRAGMA 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BUSYHANDLER 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TEMPFILENAME 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_MMAP_SIZE 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_TRACE 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_HAS_MOVED 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SYNC 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_PHASETWO 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_SET_HANDLE 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WAL_BLOCK 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ZIPVFS 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RBU 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_VFS_POINTER 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_JOURNAL_POINTER 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_WIN32_GET_HANDLE 29L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_PDB 30L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_LOCK_TIMEOUT 34L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_DATA_VERSION 35L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_SIZE_LIMIT 36L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_DONE 37L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESERVE_BYTES 38L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKPT_START 39L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_EXTERNAL_READER 40L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_CKSM_FILE 41L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_FCNTL_RESET_CACHE 42L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_NONE 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_SHARED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_RESERVED 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_PENDING 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCK_EXCLUSIVE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC512 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC1K 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC2K 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC4K 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC8K 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC16K 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC32K 128L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_ATOMIC64K 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SAFE_APPEND 512L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_SEQUENTIAL 1024L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_POWERSAFE_OVERWRITE 4096L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_IMMUTABLE 8192L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOCAP_BATCH_ATOMIC 16384L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READONLY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_READWRITE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_CREATE 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_URI 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MEMORY 128L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOMUTEX 32768L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_FULLMUTEX 65536L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SHAREDCACHE 131072L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_PRIVATECACHE 262144L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXRESCODE 33554432L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_NOFOLLOW 16777216L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_DB 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_MAIN_JOURNAL 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_DB 512L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TEMP_JOURNAL 4096L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_TRANSIENT_DB 1024L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUBJOURNAL 8192L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_SUPER_JOURNAL 16384L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_WAL 524288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_DELETEONCLOSE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE +#define org_sqlite_jni_SQLite3Jni_SQLITE_OPEN_EXCLUSIVE 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_PERSISTENT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NORMALIZE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_PREPARE_NO_VTAB 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK +#define org_sqlite_jni_SQLite3Jni_SQLITE_OK 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERNAL 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PERM +#define org_sqlite_jni_SQLite3Jni_SQLITE_PERM 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOMEM 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INTERRUPT 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR 10L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT 11L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTFOUND 12L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FULL 13L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN 14L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL +#define org_sqlite_jni_SQLite3Jni_SQLITE_PROTOCOL 15L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY +#define org_sqlite_jni_SQLite3Jni_SQLITE_EMPTY 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA +#define org_sqlite_jni_SQLite3Jni_SQLITE_SCHEMA 17L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG +#define org_sqlite_jni_SQLite3Jni_SQLITE_TOOBIG 18L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT 19L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_MISMATCH 20L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_MISUSE 21L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOLFS 22L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH +#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH 23L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_FORMAT 24L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_RANGE +#define org_sqlite_jni_SQLite3Jni_SQLITE_RANGE 25L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTADB 26L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE 27L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING +#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING 28L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROW +#define org_sqlite_jni_SQLite3Jni_SQLITE_ROW 100L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DONE 101L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_MISSING_COLLSEQ 257L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_RETRY 513L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_ERROR_SNAPSHOT 769L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_READ 266L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHORT_READ 522L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_WRITE 778L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSYNC 1034L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_FSYNC 1290L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_TRUNCATE 1546L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_FSTAT 1802L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_UNLOCK 2058L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_RDLOCK 2314L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE 2570L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BLOCKED 2826L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_NOMEM 3082L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ACCESS 3338L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CHECKRESERVEDLOCK 3594L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_LOCK 3850L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CLOSE 4106L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DIR_CLOSE 4362L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMOPEN 4618L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMSIZE 4874L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMLOCK 5130L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SHMMAP 5386L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_SEEK 5642L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DELETE_NOENT 5898L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_MMAP 6154L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_GETTEMPPATH 6410L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CONVPATH 6666L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_VNODE 6922L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_AUTH 7178L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_BEGIN_ATOMIC 7434L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_COMMIT_ATOMIC 7690L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_ROLLBACK_ATOMIC 7946L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_DATA 8202L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS +#define org_sqlite_jni_SQLite3Jni_SQLITE_IOERR_CORRUPTFS 8458L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_SHAREDCACHE 262L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_LOCKED_VTAB 518L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_RECOVERY 261L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_SNAPSHOT 517L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT +#define org_sqlite_jni_SQLite3Jni_SQLITE_BUSY_TIMEOUT 773L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_NOTEMPDIR 270L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_ISDIR 526L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_FULLPATH 782L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_CONVPATH 1038L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CANTOPEN_SYMLINK 1550L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_VTAB 267L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_SEQUENCE 523L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_CORRUPT_INDEX 779L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_RECOVERY 264L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTLOCK 520L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_ROLLBACK 776L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DBMOVED 1032L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_CANTINIT 1288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY +#define org_sqlite_jni_SQLite3Jni_SQLITE_READONLY_DIRECTORY 1544L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_ABORT_ROLLBACK 516L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_CHECK 275L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_COMMITHOOK 531L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FOREIGNKEY 787L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_FUNCTION 1043L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_NOTNULL 1299L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PRIMARYKEY 1555L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_TRIGGER 1811L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_UNIQUE 2067L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_VTAB 2323L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_ROWID 2579L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_PINNED 2835L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE +#define org_sqlite_jni_SQLite3Jni_SQLITE_CONSTRAINT_DATATYPE 3091L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_WAL 283L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_NOTICE_RECOVER_ROLLBACK 539L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_WARNING_AUTOINDEX 284L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER +#define org_sqlite_jni_SQLite3Jni_SQLITE_AUTH_USER 279L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_OK_LOAD_PERMANENTLY 256L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY +#define org_sqlite_jni_SQLite3Jni_SQLITE_SERIALIZE_NOCOPY 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_FREEONCLOSE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_READONLY 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE +#define org_sqlite_jni_SQLite3Jni_SQLITE_DESERIALIZE_RESIZEABLE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_CONFIG_STRMSIZE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_SESSION_OBJCONFIG_SIZE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MEMORY_USED 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_USED 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_OVERFLOW 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_SIZE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PARSER_STACK 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_PAGECACHE_SIZE 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATUS_MALLOC_COUNT 9L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FULLSCAN_STEP 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_SORT 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_AUTOINDEX 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_VM_STEP 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_REPREPARE 5L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_RUN 6L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_MISS 7L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_FILTER_HIT 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED +#define org_sqlite_jni_SQLite3Jni_SQLITE_STMTSTATUS_MEMUSED 99L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_NORMAL 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_FULL 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_SYNC_DATAONLY 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_STMT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_PROFILE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_ROW 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRACE_CLOSE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_NONE 0L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_READ 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE +#define org_sqlite_jni_SQLite3Jni_SQLITE_TXN_WRITE 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC +#define org_sqlite_jni_SQLite3Jni_SQLITE_DETERMINISTIC 2048L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_DIRECTONLY 524288L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_INNOCUOUS 2097152L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_SCAN_UNIQUE 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_EQ 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GT 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LE 8L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LT 16L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GE 32L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_MATCH 64L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIKE 65L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_GLOB 66L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_REGEXP 67L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_NE 68L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOT 69L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_ISNULL 71L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_IS 72L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_LIMIT 73L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_OFFSET 74L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION +#define org_sqlite_jni_SQLite3Jni_SQLITE_INDEX_CONSTRAINT_FUNCTION 150L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_CONSTRAINT_SUPPORT 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_INNOCUOUS 2L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_DIRECTONLY 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS +#define org_sqlite_jni_SQLite3Jni_SQLITE_VTAB_USES_ALL_SCHEMAS 4L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK +#define org_sqlite_jni_SQLite3Jni_SQLITE_ROLLBACK 1L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_FAIL +#define org_sqlite_jni_SQLite3Jni_SQLITE_FAIL 3L +#undef org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE +#define org_sqlite_jni_SQLite3Jni_SQLITE_REPLACE 5L +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: init + * Signature: (Lorg/sqlite/jni/SQLite3Jni;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_blob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1blob + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_double + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;ID)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1double + (JNIEnv *, jclass, jobject, jint, jdouble); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_int + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1int64 + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_null + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1null + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_parameter_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_parameter_index + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1parameter_1index + (JNIEnv *, jclass, jobject, jbyteArray); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_text + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I[BI)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1text + (JNIEnv *, jclass, jobject, jint, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_zeroblob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;II)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob + (JNIEnv *, jclass, jobject, jint, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_bind_zeroblob64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;IJ)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64 + (JNIEnv *, jclass, jobject, jint, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_busy_timeout + * Signature: (Lorg/sqlite/jni/sqlite3;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_changes + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_changes64 + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1changes64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_clear_bindings + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1clear_1bindings + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_close + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_close_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1close_1v2 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_blob + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1blob + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_bytes + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_bytes16 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1bytes16 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_double + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)D + */ +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1double + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_int + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1int64 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_database_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1database_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_origin_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1origin_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_table_name + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_1name + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_text_utf8 + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text_1utf8 + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_type + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1type + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_column_value + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Lorg/sqlite/jni/sqlite3_value; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_context_db_handle + * Signature: (Lorg/sqlite/jni/sqlite3_context;)Lorg/sqlite/jni/sqlite3; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1handle + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_compileoption_get + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1get + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_compileoption_used + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1compileoption_1used + (JNIEnv *, jclass, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_create_collation + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;ILorg/sqlite/jni/Collation;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1collation + (JNIEnv *, jclass, jobject, jstring, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_create_function + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;IILorg/sqlite/jni/SQLFunction;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1function + (JNIEnv *, jclass, jobject, jstring, jint, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_data_count + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errcode + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errcode + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_extended_errcode + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1errcode + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_extended_result_codes + * Signature: (Lorg/sqlite/jni/sqlite3;Z)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1extended_1result_1codes + (JNIEnv *, jclass, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errmsg + * Signature: (Lorg/sqlite/jni/sqlite3;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errmsg + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_errstr + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errstr + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_error_offset + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1error_1offset + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_finalize + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1finalize + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_initialize + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1initialize + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_last_insert_rowid + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1last_1insert_1rowid + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_libversion + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_libversion_number + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion_1number + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_open + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open + (JNIEnv *, jclass, jstring, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_open_v2 + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2 + (JNIEnv *, jclass, jstring, jobject, jint, jstring); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare + (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 + (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_prepare_v3 + * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 + (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_reset + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_double + * Signature: (Lorg/sqlite/jni/sqlite3_context;D)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1double + (JNIEnv *, jclass, jobject, jdouble); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_toobig + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1toobig + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_nomem + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1nomem + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_error_code + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1error_1code + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_null + * Signature: (Lorg/sqlite/jni/sqlite3_context;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1null + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_int + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;J)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1int64 + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_context;Ljava/lang/Object;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1java_1object + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_value + * Signature: (Lorg/sqlite/jni/sqlite3_context;Lorg/sqlite/jni/sqlite3_value;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1value + (JNIEnv *, jclass, jobject, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_zeroblob + * Signature: (Lorg/sqlite/jni/sqlite3_context;I)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob + (JNIEnv *, jclass, jobject, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_zeroblob64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;J)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1zeroblob64 + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_blob + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_blob64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJ)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1blob64 + (JNIEnv *, jclass, jobject, jbyteArray, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_text + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text + (JNIEnv *, jclass, jobject, jbyteArray, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_result_text64 + * Signature: (Lorg/sqlite/jni/sqlite3_context;[BJI)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 + (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_set_last_insert_rowid + * Signature: (Lorg/sqlite/jni/sqlite3;J)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1last_1insert_1rowid + (JNIEnv *, jclass, jobject, jlong); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sleep + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sleep + (JNIEnv *, jclass, jint); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sourceid + * Signature: ()Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_step + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_threadsafe + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1threadsafe + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_total_changes + * Signature: (Lorg/sqlite/jni/sqlite3;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_total_changes64 + * Signature: (Lorg/sqlite/jni/sqlite3;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_trace_v2 + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/Tracer;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_blob + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1blob + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_bytes + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_bytes16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1bytes16 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_double + * Signature: (Lorg/sqlite/jni/sqlite3_value;)D + */ +JNIEXPORT jdouble JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1double + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_dupe + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Lorg/sqlite/jni/sqlite3_value; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1dupe + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_encoding + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1encoding + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_free + * Signature: (Lorg/sqlite/jni/sqlite3_value;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1free + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_int + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_int64 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1int64 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_java_object + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1java_1object + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text + * Signature: (Lorg/sqlite/jni/sqlite3_value;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text_utf8 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text_1utf8 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16 + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16 + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16le + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16le + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_text16be + * Signature: (Lorg/sqlite/jni/sqlite3_value;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1text16be + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_type + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1type + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_numeric_type + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1numeric_1type + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_nochange + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1nochange + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_frombind + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1frombind + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_value_subtype + * Signature: (Lorg/sqlite/jni/sqlite3_value;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype + (JNIEnv *, jclass, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_shutdown + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/jni/src/org/sqlite/jni/Collation.java b/ext/jni/src/org/sqlite/jni/Collation.java new file mode 100644 index 0000000000..a05b8ef9ef --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Collation.java @@ -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() {} +} diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java new file mode 100644 index 0000000000..d6543baf57 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java @@ -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 { + 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; } +} diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java new file mode 100644 index 0000000000..f4f2269a10 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -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 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;} + } +} diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/ProgressHandler.java new file mode 100644 index 0000000000..b3e8abc0c0 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ProgressHandler.java @@ -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(); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java new file mode 100644 index 0000000000..482bf45338 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -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() {} + } +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java new file mode 100644 index 0000000000..53b920f808 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -0,0 +1,1202 @@ +/* +** 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 declares JNI bindings for the sqlite3 C API. +*/ +package org.sqlite.jni; +import java.nio.charset.StandardCharsets; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +/** + This annotation is for flagging parameters which may legally be + null, noting that they may behave different if passed null but are + prepared to expect null as a value. + + This annotation is solely for the reader's information. +*/ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@interface Nullable{} + +/** + This annotation is for flagging parameters which may not legally be + null. Note that the C-style API does _not_ throw any + NullPointerExceptions on its own because it has a no-throw policy + in order to retain its C-style semantics. + + This annotation is solely for the reader's information. No policy + is in place to programmatically ensure that NotNull is conformed to + in client code. +*/ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +@interface NotNull{} + +/** + This class contains the entire sqlite3 JNI API binding. For + client-side use, a static import is recommended: + + ``` + import static org.sqlite.jni.SQLite3Jni.*; + ``` + + The C-side part can be found in sqlite3-jni.c. + + + Only functions which materially differ from their C counterparts + are documented here. The C documetation is otherwise applicable + here: + + https://sqlite.org/c3ref/intro.html + + A small handful of Java-specific APIs have been added. +*/ +public final class SQLite3Jni { + static { + System.loadLibrary("sqlite3-jni"); + } + private SQLite3Jni(){} + private static native void init(@NotNull SQLite3Jni s); + + ////////////////////////////////////////////////////////////////////// + // Maintenance reminder: please keep the functions alphabetized. + // The SQLITE_... values. on the other hand, are grouped by category. + + public static int sqlite3_bind_blob(@NotNull sqlite3_stmt stmt, int ndx, + @Nullable byte[] data){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_blob(stmt, ndx, data, data.length); + } + + private static native int sqlite3_bind_blob(@NotNull sqlite3_stmt stmt, + int ndx, @Nullable byte[] data, + int n); + + public static native int sqlite3_bind_double(@NotNull sqlite3_stmt stmt, + int ndx, double v); + + public static native int sqlite3_bind_int(@NotNull sqlite3_stmt stmt, + int ndx, int v); + + public static native int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, + int ndx, long v); + + public static native int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx); + + public static native int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt); + + + private static native int sqlite3_bind_parameter_index(@NotNull sqlite3_stmt stmt, + byte[] paramName); + + public static int sqlite3_bind_parameter_index(@NotNull sqlite3_stmt stmt, + @NotNull String paramName){ + final byte[] utf8 = (paramName+"\0").getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_parameter_index(stmt, utf8); + } + + public static int sqlite3_bind_text(@NotNull sqlite3_stmt stmt, int ndx, + @Nullable String data){ + if(null == data) return sqlite3_bind_null(stmt, ndx); + final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8); + return sqlite3_bind_text(stmt, ndx, utf8, utf8.length); + } + + public static int sqlite3_bind_text(@NotNull sqlite3_stmt stmt, int ndx, + @Nullable byte[] data){ + return (null == data) + ? sqlite3_bind_null(stmt, ndx) + : sqlite3_bind_text(stmt, ndx, data, data.length); + } + + /** + Works like the C-level sqlite3_bind_text() but (A) assumes + SQLITE_TRANSIENT for the final parameter and (B) behaves like + sqlite3_bind_null() if the data argument is null. + */ + private static native int sqlite3_bind_text(@NotNull sqlite3_stmt stmt, int ndx, + @Nullable byte[] data, int maxBytes); + + public static native int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n); + + public static native int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n); + + //TODO? public static native int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); + + public static native int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms); + + public static native int sqlite3_changes(@NotNull sqlite3 db); + + public static native long sqlite3_changes64(@NotNull sqlite3 db); + + public static native int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt); + + public static native int sqlite3_close(@NotNull sqlite3 db); + + public static native int sqlite3_close_v2(@NotNull sqlite3 db); + + //TODO? public static native int sqlite3_collation_needed(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const char*)); + //TODO? public static native int sqlite3_collation_needed16(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const void*)); + + public static native byte[] sqlite3_column_blob(@NotNull sqlite3_stmt stmt, int ndx); + + public static native int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx); + + public static native int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx); + + public static native int sqlite3_column_count(@NotNull sqlite3_stmt stmt); + + public static native double sqlite3_column_double(@NotNull sqlite3_stmt stmt, int ndx); + + public static native int sqlite3_column_int(@NotNull sqlite3_stmt stmt, int ndx); + + public static native long sqlite3_column_int64(@NotNull sqlite3_stmt stmt, int ndx); + + public static native String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx); + + public static native String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx); + + /** + Column counterpart of sqlite3_value_java_object(). + */ + public static Object sqlite3_column_java_object(@NotNull sqlite3_stmt stmt, + int ndx){ + Object rv = null; + sqlite3_value v = sqlite3_column_value(stmt, ndx); + if(null!=v){ + v = sqlite3_value_dupe(v) /* we need a "protected" value */; + if(null!=v){ + rv = sqlite3_value_java_object(v); + sqlite3_value_free(v); + } + } + return rv; + } + + /** + Column counterpart of sqlite3_value_java_casted(). + */ + @SuppressWarnings("unchecked") + public static T sqlite3_column_java_casted(@NotNull sqlite3_stmt stmt, + int ndx, + @NotNull Class type){ + final Object o = sqlite3_column_java_object(stmt, ndx); + return (null!=o && type.isInstance(o)) ? (T)o : null; + } + + public static native String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx); + + public static native String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx); + + /** + Because Java strings use UTF-16 and JNI speaks Modified UTF-8 + instead of standard UTF8[^1], this routine functions equivalently to + the native sqlite3_column_text16(), so requires conversion from + the db if the db uses the default encoding of UTF-8. + + To extract _standard_ UTF-8, use sqlite3_column_text_utf8(). + This API includes no functions for working with Modified UTF-8. + + [^1]: https://stackoverflow.com/questions/7921016 + */ + public static native String sqlite3_column_text(@NotNull sqlite3_stmt stmt, int ndx); + + /** + Similar to sqlite3_column_text(), but the result is an array encoded + in standard UTF-8, not Modified UTF-8. + */ + public static native byte[] sqlite3_column_text_utf8(@NotNull sqlite3_stmt stmt, + int ndx); + //TODO public static native ?type? sqlite3_column_text16(@NotNull sqlite3_stmt stmt, int ndx); + //TODO: public static Object sqlite3_column_to_java(@NotNull sqlite3_value v){...} + + public static native int sqlite3_column_type(@NotNull sqlite3_stmt stmt, + int ndx); + + public static native sqlite3_value sqlite3_column_value(@NotNull sqlite3_stmt stmt, + int ndx); + + // TODO public static native int sqlite3_collation_needed( + //sqlite3 db, void(*)(void*,sqlite3*,int eTextRep,const char*)) + + //TODO public static native int sqlite3_collation_needed16( + // sqlite3 db, void(*)(void*,sqlite3*,int eTextRep,const void*) + + public static native sqlite3 sqlite3_context_db_handle(@NotNull sqlite3_context cx); + + //TODO? void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); + + public static native String sqlite3_compileoption_get(int n); + + public static native boolean sqlite3_compileoption_used(@NotNull String optName); + + public static native int sqlite3_create_collation(@NotNull sqlite3 db, + @NotNull String name, + int eTextRep, + @NotNull Collation col); + + //! Convenience overload which assumes SQLITE_UTF8 encoding. + public static int sqlite3_create_collation(@NotNull sqlite3 db, + @NotNull String name, + @NotNull Collation col){ + return sqlite3_create_collation(db, name, SQLITE_UTF8, col); + } + + public static native int sqlite3_create_function(@NotNull sqlite3 db, + @NotNull String functionName, + int nArg, int eTextRep, + @NotNull SQLFunction func); + + public static native int sqlite3_data_count(@NotNull sqlite3_stmt stmt); + + public static native int sqlite3_errcode(@NotNull sqlite3 db); + + public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); + + public static native boolean sqlite3_extended_result_codes(@NotNull sqlite3 db, + boolean onoff); + + public static native String sqlite3_errmsg(@NotNull sqlite3 db); + + public static native String sqlite3_errstr(int resultCode); + + public static native int sqlite3_error_offset(@NotNull sqlite3 db); + + public static native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); + + public static native int sqlite3_initialize(); + + public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + + //TODO? void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + + public static native String sqlite3_libversion(); + + public static native int sqlite3_libversion_number(); + + public static native int sqlite3_open(@Nullable String filename, + @NotNull sqlite3 ppDb); + + public static native int sqlite3_open_v2(@Nullable String filename, + @NotNull sqlite3 ppDb, + int flags, @Nullable String zVfs); + + /** + The sqlite3_prepare() family of functions require slightly + different signatures than their native counterparts, but + overloading allows us to install several convenience forms. + + All of them which take their SQL in the form of a byte[] require + that it be in UTF-8 encoding unless explicitly noted otherwise. + + The forms which take a "tail" output pointer return (via that + output object) the index into their SQL byte array at which the + end of the first SQL statement processed by the call was + found. That's fundamentally how the C APIs work but making use of + that value requires more copying of the input SQL into + consecutively smaller arrays in order to consume all of + it. (There is an example of doing that in this project's Tester1 + class.) For that vast majority of uses, that capability is not + necessary, however, and overloads are provided which gloss over + that. + */ + private static native int sqlite3_prepare(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset); + + public static int sqlite3_prepare(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, + @NotNull sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset){ + return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); + } + + public static int sqlite3_prepare(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, + @NotNull sqlite3_stmt outStmt){ + return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, null); + } + + public static int sqlite3_prepare(@NotNull sqlite3 db, @NotNull String sql, + @NotNull sqlite3_stmt outStmt){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); + } + + private static native int sqlite3_prepare_v2(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, + int maxBytes, + @NotNull sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset); + + public static int sqlite3_prepare_v2(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset){ + return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v2(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, + @NotNull sqlite3_stmt outStmt){ + return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, null); + } + + public static int sqlite3_prepare_v2(@NotNull sqlite3 db, + @NotNull String sql, + @NotNull sqlite3_stmt outStmt){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); + } + + private static native int sqlite3_prepare_v3(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, + int maxBytes, int prepFlags, + @NotNull sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset); + + public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + int prepFlags, + @NotNull sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset){ + return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, pTailOffset); + } + + public static int sqlite3_prepare_v3(@NotNull sqlite3 db, + @NotNull byte[] sqlUtf8, + int prepFlags, + @NotNull sqlite3_stmt outStmt){ + return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, null); + } + + public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull String sql, + int prepFlags, @NotNull sqlite3_stmt outStmt){ + final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); + return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); + } + + + //TODO public static native void sqlite3_progress_handler(sqlite3 db, int n, ProgressHandler h); + + public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + + public static native void sqlite3_result_double(@NotNull sqlite3_context cx, double v); + + /** + The main sqlite3_result_error() impl of which all others are + proxies. eTextRep must be one of SQLITE_UTF8 or SQLITE_UTF16 and + msg must be encoded correspondingly. Any other eTextRep value + results in the C-level sqlite3_result_error() being called with + a complaint about the invalid argument. + */ + private static native void sqlite3_result_error(@NotNull sqlite3_context cx, + @Nullable byte[] msg, + int eTextRep); + + public static void sqlite3_result_error(@NotNull sqlite3_context cx, + @NotNull byte[] utf8){ + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error(@NotNull sqlite3_context cx, + @NotNull String msg){ + final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_8); + sqlite3_result_error(cx, utf8, SQLITE_UTF8); + } + + public static void sqlite3_result_error16(@NotNull sqlite3_context cx, + @Nullable byte[] utf16){ + sqlite3_result_error(cx, utf16, SQLITE_UTF16); + } + + public static void sqlite3_result_error16(@NotNull sqlite3_context cx, + @NotNull String msg){ + final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_16); + sqlite3_result_error(cx, utf8, SQLITE_UTF16); + } + + public static void sqlite3_result_error(@NotNull sqlite3_context cx, + @NotNull Exception e){ + sqlite3_result_error(cx, e.getMessage()); + } + + public static void sqlite3_result_error16(@NotNull sqlite3_context cx, + @NotNull Exception e){ + sqlite3_result_error16(cx, e.getMessage()); + } + + public static native void sqlite3_result_error_toobig(@NotNull sqlite3_context cx); + + public static native void sqlite3_result_error_nomem(@NotNull sqlite3_context cx); + + public static native void sqlite3_result_error_code(@NotNull sqlite3_context cx, int c); + + public static native void sqlite3_result_null(@NotNull sqlite3_context cx); + + public static native void sqlite3_result_int(@NotNull sqlite3_context cx, int v); + + public static native void sqlite3_result_int64(@NotNull sqlite3_context cx, long v); + + /** + Binds the SQL result to the given object, or + sqlite3_result_null() if o is null. Use + sqlite3_value_java_object() or sqlite3_column_java_object() to + fetch it. + + This is implemented in terms of sqlite3_result_pointer(), but + that function is not exposed to JNI because its 3rd argument must + be a constant string (the library does not copy it), which we + cannot implement cross-language here unless, in the JNI layer, we + allocate such strings and store them somewhere for long-term use + (leaking them more likely than not). Even then, passing around a + pointer via Java like that has little practical use. + */ + public static native void sqlite3_result_java_object(@NotNull sqlite3_context cx, + @NotNull Object o); + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, + @NotNull Integer v){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){ + sqlite3_result_int(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, + @NotNull Boolean v){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, boolean v){ + sqlite3_result_int(cx, v ? 1 : 0); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, + @NotNull Long v){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, long v){ + sqlite3_result_int64(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, + @NotNull Double v){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, double v){ + sqlite3_result_double(cx, v); + } + + public static void sqlite3_result_set(@NotNull sqlite3_context cx, + @Nullable String v){ + sqlite3_result_text(cx, v); + } + + public static native void sqlite3_result_value(@NotNull sqlite3_context cx, + @NotNull sqlite3_value v); + + public static native void sqlite3_result_zeroblob(@NotNull sqlite3_context cx, + int n); + + public static native int sqlite3_result_zeroblob64(@NotNull sqlite3_context cx, + long n); + + private static native void sqlite3_result_blob(@NotNull sqlite3_context cx, + @Nullable byte[] blob, + int maxLen); + + public static void sqlite3_result_blob(@NotNull sqlite3_context cx, @Nullable byte[] blob){ + sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); + } + + /** + Binds the given text using C's sqlite3_result_blob64() unless: + + - blob is null ==> sqlite3_result_null() + + - blob is too large ==> sqlite3_result_error_toobig() + + If maxLen is larger than blob.length, it is truncated to that + value. If it is negative, results are undefined. + */ + private static native void sqlite3_result_blob64(@NotNull sqlite3_context cx, + @Nullable byte[] blob, + long maxLen); + + public static void sqlite3_result_blob64(@NotNull sqlite3_context cx, + @Nullable byte[] blob){ + sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); + } + + private static native void sqlite3_result_text(@NotNull sqlite3_context cx, + @Nullable byte[] text, int maxLen); + + public static void sqlite3_result_text(@NotNull sqlite3_context cx, + @Nullable byte[] text){ + sqlite3_result_text(cx, text, null==text ? 0 : text.length); + } + + public static void sqlite3_result_text(@NotNull sqlite3_context cx, + @Nullable String text){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); + sqlite3_result_text(cx, utf8, utf8.length); + } + } + + /** + Binds the given text using C's sqlite3_result_text64() unless: + + - text is null ==> sqlite3_result_null() + + - text is too large ==> sqlite3_result_error_toobig() + + - The `encoding` argument has an invalid value ==> + sqlite3_result_error_code() with SQLITE_FORMAT + + If maxLength (in bytes, not characters) is larger than + text.length, it is silently truncated to text.length. If it is + negative, results are undefined. + */ + private static native void sqlite3_result_text64(@NotNull sqlite3_context cx, + @Nullable byte[] text, + long maxLength, int encoding); + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16 using the platform's byte order. + */ + public static void sqlite3_result_text16(@NotNull sqlite3_context cx, + @Nullable byte[] text){ + sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16); + } + + public static void sqlite3_result_text16(@NotNull sqlite3_context cx, + @Nullable String text){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16); + } + } + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16LE. + */ + public static void sqlite3_result_text16le(@NotNull sqlite3_context cx, + @Nullable String text){ + if(null == text) sqlite3_result_null(cx); + else{ + final byte[] b = text.getBytes(StandardCharsets.UTF_16LE); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16LE); + } + } + + /** + Sets the current UDF result to the given bytes, which are assumed + be encoded in UTF-16BE. + */ + public static void sqlite3_result_text16be(@NotNull sqlite3_context cx, + @Nullable byte[] text){ + sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16BE); + } + + public static void sqlite3_result_text16be(@NotNull sqlite3_context cx, + @NotNull String text){ + final byte[] b = text.getBytes(StandardCharsets.UTF_16BE); + sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); + } + + /** + public static void sqlite3_result_text64(@NotNull sqlite3_context cx, + @Nullable byte[] text){ + sqlite3_result_text64(cx, text, null==text ? 0 : text.length, SQLITE_UTF8); + }**/ + + public static native void sqlite3_set_last_insert_rowid(@NotNull sqlite3 db, long rowid); + + public static native int sqlite3_sleep(int ms); + + public static native String sqlite3_sourceid(); + + public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + + public static native int sqlite3_threadsafe(); + + public static native int sqlite3_total_changes(@NotNull sqlite3 db); + + public static native long sqlite3_total_changes64(@NotNull sqlite3 db); + + /** + Works like C's sqlite3_trace_v2() except that the 3rd argument to that + function is elided here because the roles of that functions' 3rd and 4th + arguments are encapsulated in the final argument to this function. + + Unlike the C API, which is documented as always returning 0, this + implementation returns SQLITE_NOMEM if allocation of per-db + mapping state fails and SQLITE_ERROR if the given callback object + cannot be processed propertly (i.e. an internal error). + */ + public static native int sqlite3_trace_v2(@NotNull sqlite3 db, int traceMask, + @Nullable Tracer tracer); + + public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); + + public static native int sqlite3_value_bytes(@NotNull sqlite3_value v); + + public static native int sqlite3_value_bytes16(@NotNull sqlite3_value v); + + public static native double sqlite3_value_double(@NotNull sqlite3_value v); + + public static native sqlite3_value sqlite3_value_dupe(@NotNull sqlite3_value v); + + public static native int sqlite3_value_encoding(@NotNull sqlite3_value v); + + public static native void sqlite3_value_free(@Nullable sqlite3_value v); + + public static native int sqlite3_value_int(@NotNull sqlite3_value v); + + public static native long sqlite3_value_int64(@NotNull sqlite3_value v); + + /** + If the given value was set using sqlite3_result_java_value() then + this function returns that object, else it returns null. + + It is up to the caller to inspect the object to determine its + type, and cast it if necessary. + */ + public static native Object sqlite3_value_java_object(@NotNull sqlite3_value v); + + /** + A variant of sqlite3_value_java_object() which returns the + fetched object cast to T if the object is an instance of the + given Class. It returns null in all other cases. + */ + @SuppressWarnings("unchecked") + public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, + @NotNull Class type){ + final Object o = sqlite3_value_java_object(v); + return (null!=o && type.isInstance(o)) ? (T)o : null; + } + + /** + See sqlite3_column_text() for notes about encoding conversions. + See sqlite3_value_text_utf8() for how to extract text in standard + UTF-8. + */ + public static native String sqlite3_value_text(@NotNull sqlite3_value v); + + /** + The sqlite3_value counterpart of sqlite3_column_text_utf8(). + */ + public static native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); + + public static native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); + + public static native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); + + public static native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); + + //TODO: to_java() should return a closest-match type for the given + //value. The quirk is that it would need to return object-type values, + //e.g. Integer instead of int, and creating those is a bit of a nuisance + //from JNI. + //public static native Object sqlite3_value_to_java(@NotNull sqlite3_value v); + // Or we can just implement it in Java: + //public static Object sqlite3_value_to_java(@NotNull sqlite3_value v){...} + + public static native int sqlite3_value_type(@NotNull sqlite3_value v); + + public static native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); + + public static native int sqlite3_value_nochange(@NotNull sqlite3_value v); + + public static native int sqlite3_value_frombind(@NotNull sqlite3_value v); + + public static native int sqlite3_value_subtype(@NotNull sqlite3_value v); + + public static native int sqlite3_shutdown(); + + ////////////////////////////////////////////////////////////////////// + // SQLITE_... constants follow... + + // version info + public static final int SQLITE_VERSION_NUMBER = sqlite3_libversion_number(); + public static final String SQLITE_VERSION = sqlite3_libversion(); + public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); + + // access + public static final int SQLITE_ACCESS_EXISTS = 0; + public static final int SQLITE_ACCESS_READWRITE = 1; + public static final int SQLITE_ACCESS_READ = 2; + + // authorizer + public static final int SQLITE_DENY = 1; + public static final int SQLITE_IGNORE = 2; + public static final int SQLITE_CREATE_INDEX = 1; + public static final int SQLITE_CREATE_TABLE = 2; + public static final int SQLITE_CREATE_TEMP_INDEX = 3; + public static final int SQLITE_CREATE_TEMP_TABLE = 4; + public static final int SQLITE_CREATE_TEMP_TRIGGER = 5; + public static final int SQLITE_CREATE_TEMP_VIEW = 6; + public static final int SQLITE_CREATE_TRIGGER = 7; + public static final int SQLITE_CREATE_VIEW = 8; + public static final int SQLITE_DELETE = 9; + public static final int SQLITE_DROP_INDEX = 10; + public static final int SQLITE_DROP_TABLE = 11; + public static final int SQLITE_DROP_TEMP_INDEX = 12; + public static final int SQLITE_DROP_TEMP_TABLE = 13; + public static final int SQLITE_DROP_TEMP_TRIGGER = 14; + public static final int SQLITE_DROP_TEMP_VIEW = 15; + public static final int SQLITE_DROP_TRIGGER = 16; + public static final int SQLITE_DROP_VIEW = 17; + public static final int SQLITE_INSERT = 18; + public static final int SQLITE_PRAGMA = 19; + public static final int SQLITE_READ = 20; + public static final int SQLITE_SELECT = 21; + public static final int SQLITE_TRANSACTION = 22; + public static final int SQLITE_UPDATE = 23; + public static final int SQLITE_ATTACH = 24; + public static final int SQLITE_DETACH = 25; + public static final int SQLITE_ALTER_TABLE = 26; + public static final int SQLITE_REINDEX = 27; + public static final int SQLITE_ANALYZE = 28; + public static final int SQLITE_CREATE_VTABLE = 29; + public static final int SQLITE_DROP_VTABLE = 30; + public static final int SQLITE_FUNCTION = 31; + public static final int SQLITE_SAVEPOINT = 32; + public static final int SQLITE_RECURSIVE = 33; + + // blob finalizers: + public static final int SQLITE_STATIC = 0; + public static final int SQLITE_TRANSIENT = -1; + + // changeset + public static final int SQLITE_CHANGESETSTART_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_NOSAVEPOINT = 1; + public static final int SQLITE_CHANGESETAPPLY_INVERT = 2; + public static final int SQLITE_CHANGESETAPPLY_IGNORENOOP = 4; + public static final int SQLITE_CHANGESET_DATA = 1; + public static final int SQLITE_CHANGESET_NOTFOUND = 2; + public static final int SQLITE_CHANGESET_CONFLICT = 3; + public static final int SQLITE_CHANGESET_CONSTRAINT = 4; + public static final int SQLITE_CHANGESET_FOREIGN_KEY = 5; + public static final int SQLITE_CHANGESET_OMIT = 0; + public static final int SQLITE_CHANGESET_REPLACE = 1; + public static final int SQLITE_CHANGESET_ABORT = 2; + + // config + public static final int SQLITE_CONFIG_SINGLETHREAD = 1; + public static final int SQLITE_CONFIG_MULTITHREAD = 2; + public static final int SQLITE_CONFIG_SERIALIZED = 3; + public static final int SQLITE_CONFIG_MALLOC = 4; + public static final int SQLITE_CONFIG_GETMALLOC = 5; + public static final int SQLITE_CONFIG_SCRATCH = 6; + public static final int SQLITE_CONFIG_PAGECACHE = 7; + public static final int SQLITE_CONFIG_HEAP = 8; + public static final int SQLITE_CONFIG_MEMSTATUS = 9; + public static final int SQLITE_CONFIG_MUTEX = 10; + public static final int SQLITE_CONFIG_GETMUTEX = 11; + public static final int SQLITE_CONFIG_LOOKASIDE = 13; + public static final int SQLITE_CONFIG_PCACHE = 14; + public static final int SQLITE_CONFIG_GETPCACHE = 15; + public static final int SQLITE_CONFIG_LOG = 16; + public static final int SQLITE_CONFIG_URI = 17; + public static final int SQLITE_CONFIG_PCACHE2 = 18; + public static final int SQLITE_CONFIG_GETPCACHE2 = 19; + public static final int SQLITE_CONFIG_COVERING_INDEX_SCAN = 20; + public static final int SQLITE_CONFIG_SQLLOG = 21; + public static final int SQLITE_CONFIG_MMAP_SIZE = 22; + public static final int SQLITE_CONFIG_WIN32_HEAPSIZE = 23; + public static final int SQLITE_CONFIG_PCACHE_HDRSZ = 24; + public static final int SQLITE_CONFIG_PMASZ = 25; + public static final int SQLITE_CONFIG_STMTJRNL_SPILL = 26; + public static final int SQLITE_CONFIG_SMALL_MALLOC = 27; + public static final int SQLITE_CONFIG_SORTERREF_SIZE = 28; + public static final int SQLITE_CONFIG_MEMDB_MAXSIZE = 29; + + // data types + public static final int SQLITE_INTEGER = 1; + public static final int SQLITE_FLOAT = 2; + public static final int SQLITE_TEXT = 3; + public static final int SQLITE_BLOB = 4; + public static final int SQLITE_NULL = 5; + + // db config + public static final int SQLITE_DBCONFIG_MAINDBNAME = 1000; + public static final int SQLITE_DBCONFIG_LOOKASIDE = 1001; + public static final int SQLITE_DBCONFIG_ENABLE_FKEY = 1002; + public static final int SQLITE_DBCONFIG_ENABLE_TRIGGER = 1003; + public static final int SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER = 1004; + public static final int SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION = 1005; + public static final int SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE = 1006; + public static final int SQLITE_DBCONFIG_ENABLE_QPSG = 1007; + public static final int SQLITE_DBCONFIG_TRIGGER_EQP = 1008; + public static final int SQLITE_DBCONFIG_RESET_DATABASE = 1009; + public static final int SQLITE_DBCONFIG_DEFENSIVE = 1010; + public static final int SQLITE_DBCONFIG_WRITABLE_SCHEMA = 1011; + public static final int SQLITE_DBCONFIG_LEGACY_ALTER_TABLE = 1012; + public static final int SQLITE_DBCONFIG_DQS_DML = 1013; + public static final int SQLITE_DBCONFIG_DQS_DDL = 1014; + public static final int SQLITE_DBCONFIG_ENABLE_VIEW = 1015; + public static final int SQLITE_DBCONFIG_LEGACY_FILE_FORMAT = 1016; + public static final int SQLITE_DBCONFIG_TRUSTED_SCHEMA = 1017; + public static final int SQLITE_DBCONFIG_STMT_SCANSTATUS = 1018; + public static final int SQLITE_DBCONFIG_REVERSE_SCANORDER = 1019; + public static final int SQLITE_DBCONFIG_MAX = 1019; + + // db status + public static final int SQLITE_DBSTATUS_LOOKASIDE_USED = 0; + public static final int SQLITE_DBSTATUS_CACHE_USED = 1; + public static final int SQLITE_DBSTATUS_SCHEMA_USED = 2; + public static final int SQLITE_DBSTATUS_STMT_USED = 3; + public static final int SQLITE_DBSTATUS_LOOKASIDE_HIT = 4; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5; + public static final int SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6; + public static final int SQLITE_DBSTATUS_CACHE_HIT = 7; + public static final int SQLITE_DBSTATUS_CACHE_MISS = 8; + public static final int SQLITE_DBSTATUS_CACHE_WRITE = 9; + public static final int SQLITE_DBSTATUS_DEFERRED_FKS = 10; + public static final int SQLITE_DBSTATUS_CACHE_USED_SHARED = 11; + public static final int SQLITE_DBSTATUS_CACHE_SPILL = 12; + public static final int SQLITE_DBSTATUS_MAX = 12; + + // encodings + public static final int SQLITE_UTF8 = 1; + public static final int SQLITE_UTF16LE = 2; + public static final int SQLITE_UTF16BE = 3; + public static final int SQLITE_UTF16 = 4; + public static final int SQLITE_UTF16_ALIGNED = 8; + + // fcntl + public static final int SQLITE_FCNTL_LOCKSTATE = 1; + public static final int SQLITE_FCNTL_GET_LOCKPROXYFILE = 2; + public static final int SQLITE_FCNTL_SET_LOCKPROXYFILE = 3; + public static final int SQLITE_FCNTL_LAST_ERRNO = 4; + public static final int SQLITE_FCNTL_SIZE_HINT = 5; + public static final int SQLITE_FCNTL_CHUNK_SIZE = 6; + public static final int SQLITE_FCNTL_FILE_POINTER = 7; + public static final int SQLITE_FCNTL_SYNC_OMITTED = 8; + public static final int SQLITE_FCNTL_WIN32_AV_RETRY = 9; + public static final int SQLITE_FCNTL_PERSIST_WAL = 10; + public static final int SQLITE_FCNTL_OVERWRITE = 11; + public static final int SQLITE_FCNTL_VFSNAME = 12; + public static final int SQLITE_FCNTL_POWERSAFE_OVERWRITE = 13; + public static final int SQLITE_FCNTL_PRAGMA = 14; + public static final int SQLITE_FCNTL_BUSYHANDLER = 15; + public static final int SQLITE_FCNTL_TEMPFILENAME = 16; + public static final int SQLITE_FCNTL_MMAP_SIZE = 18; + public static final int SQLITE_FCNTL_TRACE = 19; + public static final int SQLITE_FCNTL_HAS_MOVED = 20; + public static final int SQLITE_FCNTL_SYNC = 21; + public static final int SQLITE_FCNTL_COMMIT_PHASETWO = 22; + public static final int SQLITE_FCNTL_WIN32_SET_HANDLE = 23; + public static final int SQLITE_FCNTL_WAL_BLOCK = 24; + public static final int SQLITE_FCNTL_ZIPVFS = 25; + public static final int SQLITE_FCNTL_RBU = 26; + public static final int SQLITE_FCNTL_VFS_POINTER = 27; + public static final int SQLITE_FCNTL_JOURNAL_POINTER = 28; + public static final int SQLITE_FCNTL_WIN32_GET_HANDLE = 29; + public static final int SQLITE_FCNTL_PDB = 30; + public static final int SQLITE_FCNTL_BEGIN_ATOMIC_WRITE = 31; + public static final int SQLITE_FCNTL_COMMIT_ATOMIC_WRITE = 32; + public static final int SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE = 33; + public static final int SQLITE_FCNTL_LOCK_TIMEOUT = 34; + public static final int SQLITE_FCNTL_DATA_VERSION = 35; + public static final int SQLITE_FCNTL_SIZE_LIMIT = 36; + public static final int SQLITE_FCNTL_CKPT_DONE = 37; + public static final int SQLITE_FCNTL_RESERVE_BYTES = 38; + public static final int SQLITE_FCNTL_CKPT_START = 39; + public static final int SQLITE_FCNTL_EXTERNAL_READER = 40; + public static final int SQLITE_FCNTL_CKSM_FILE = 41; + public static final int SQLITE_FCNTL_RESET_CACHE = 42; + + // flock + public static final int SQLITE_LOCK_NONE = 0; + public static final int SQLITE_LOCK_SHARED = 1; + public static final int SQLITE_LOCK_RESERVED = 2; + public static final int SQLITE_LOCK_PENDING = 3; + public static final int SQLITE_LOCK_EXCLUSIVE = 4; + + // iocap + public static final int SQLITE_IOCAP_ATOMIC = 1; + public static final int SQLITE_IOCAP_ATOMIC512 = 2; + public static final int SQLITE_IOCAP_ATOMIC1K = 4; + public static final int SQLITE_IOCAP_ATOMIC2K = 8; + public static final int SQLITE_IOCAP_ATOMIC4K = 16; + public static final int SQLITE_IOCAP_ATOMIC8K = 32; + public static final int SQLITE_IOCAP_ATOMIC16K = 64; + public static final int SQLITE_IOCAP_ATOMIC32K = 128; + public static final int SQLITE_IOCAP_ATOMIC64K = 256; + public static final int SQLITE_IOCAP_SAFE_APPEND = 512; + public static final int SQLITE_IOCAP_SEQUENTIAL = 1024; + public static final int SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN = 2048; + public static final int SQLITE_IOCAP_POWERSAFE_OVERWRITE = 4096; + public static final int SQLITE_IOCAP_IMMUTABLE = 8192; + public static final int SQLITE_IOCAP_BATCH_ATOMIC = 16384; + + // limits. These get injected at init-time so that they stay in sync + // with the compile-time options. This unfortunately means they are + // not final, but keeping them in sync with their C values seems + // more important than protecting users from assigning to these + // (with unpredictable results). + public static int SQLITE_MAX_ALLOCATION_SIZE = -1; + public static int SQLITE_LIMIT_LENGTH = -1; + public static int SQLITE_MAX_LENGTH = -1; + public static int SQLITE_LIMIT_SQL_LENGTH = -1; + public static int SQLITE_MAX_SQL_LENGTH = -1; + public static int SQLITE_LIMIT_COLUMN = -1; + public static int SQLITE_MAX_COLUMN = -1; + public static int SQLITE_LIMIT_EXPR_DEPTH = -1; + public static int SQLITE_MAX_EXPR_DEPTH = -1; + public static int SQLITE_LIMIT_COMPOUND_SELECT = -1; + public static int SQLITE_MAX_COMPOUND_SELECT = -1; + public static int SQLITE_LIMIT_VDBE_OP = -1; + public static int SQLITE_MAX_VDBE_OP = -1; + public static int SQLITE_LIMIT_FUNCTION_ARG = -1; + public static int SQLITE_MAX_FUNCTION_ARG = -1; + public static int SQLITE_LIMIT_ATTACHED = -1; + public static int SQLITE_MAX_ATTACHED = -1; + public static int SQLITE_LIMIT_LIKE_PATTERN_LENGTH = -1; + public static int SQLITE_MAX_LIKE_PATTERN_LENGTH = -1; + public static int SQLITE_LIMIT_VARIABLE_NUMBER = -1; + public static int SQLITE_MAX_VARIABLE_NUMBER = -1; + public static int SQLITE_LIMIT_TRIGGER_DEPTH = -1; + public static int SQLITE_MAX_TRIGGER_DEPTH = -1; + public static int SQLITE_LIMIT_WORKER_THREADS = -1; + public static int SQLITE_MAX_WORKER_THREADS = -1; + + // open flags + public static final int SQLITE_OPEN_READONLY = 1; + public static final int SQLITE_OPEN_READWRITE = 2; + public static final int SQLITE_OPEN_CREATE = 4; + public static final int SQLITE_OPEN_URI = 64; + public static final int SQLITE_OPEN_MEMORY = 128; + public static final int SQLITE_OPEN_NOMUTEX = 32768; + public static final int SQLITE_OPEN_FULLMUTEX = 65536; + public static final int SQLITE_OPEN_SHAREDCACHE = 131072; + public static final int SQLITE_OPEN_PRIVATECACHE = 262144; + public static final int SQLITE_OPEN_EXRESCODE = 33554432; + public static final int SQLITE_OPEN_NOFOLLOW = 16777216; + public static final int SQLITE_OPEN_MAIN_DB = 256; + public static final int SQLITE_OPEN_MAIN_JOURNAL = 2048; + public static final int SQLITE_OPEN_TEMP_DB = 512; + public static final int SQLITE_OPEN_TEMP_JOURNAL = 4096; + public static final int SQLITE_OPEN_TRANSIENT_DB = 1024; + public static final int SQLITE_OPEN_SUBJOURNAL = 8192; + public static final int SQLITE_OPEN_SUPER_JOURNAL = 16384; + public static final int SQLITE_OPEN_WAL = 524288; + public static final int SQLITE_OPEN_DELETEONCLOSE = 8; + public static final int SQLITE_OPEN_EXCLUSIVE = 16; + + // prepare flags + public static final int SQLITE_PREPARE_PERSISTENT = 1; + public static final int SQLITE_PREPARE_NORMALIZE = 2; + public static final int SQLITE_PREPARE_NO_VTAB = 4; + + // result codes + public static final int SQLITE_OK = 0; + public static final int SQLITE_ERROR = 1; + public static final int SQLITE_INTERNAL = 2; + public static final int SQLITE_PERM = 3; + public static final int SQLITE_ABORT = 4; + public static final int SQLITE_BUSY = 5; + public static final int SQLITE_LOCKED = 6; + public static final int SQLITE_NOMEM = 7; + public static final int SQLITE_READONLY = 8; + public static final int SQLITE_INTERRUPT = 9; + public static final int SQLITE_IOERR = 10; + public static final int SQLITE_CORRUPT = 11; + public static final int SQLITE_NOTFOUND = 12; + public static final int SQLITE_FULL = 13; + public static final int SQLITE_CANTOPEN = 14; + public static final int SQLITE_PROTOCOL = 15; + public static final int SQLITE_EMPTY = 16; + public static final int SQLITE_SCHEMA = 17; + public static final int SQLITE_TOOBIG = 18; + public static final int SQLITE_CONSTRAINT = 19; + public static final int SQLITE_MISMATCH = 20; + public static final int SQLITE_MISUSE = 21; + public static final int SQLITE_NOLFS = 22; + public static final int SQLITE_AUTH = 23; + public static final int SQLITE_FORMAT = 24; + public static final int SQLITE_RANGE = 25; + public static final int SQLITE_NOTADB = 26; + public static final int SQLITE_NOTICE = 27; + public static final int SQLITE_WARNING = 28; + public static final int SQLITE_ROW = 100; + public static final int SQLITE_DONE = 101; + public static final int SQLITE_ERROR_MISSING_COLLSEQ = 257; + public static final int SQLITE_ERROR_RETRY = 513; + public static final int SQLITE_ERROR_SNAPSHOT = 769; + public static final int SQLITE_IOERR_READ = 266; + public static final int SQLITE_IOERR_SHORT_READ = 522; + public static final int SQLITE_IOERR_WRITE = 778; + public static final int SQLITE_IOERR_FSYNC = 1034; + public static final int SQLITE_IOERR_DIR_FSYNC = 1290; + public static final int SQLITE_IOERR_TRUNCATE = 1546; + public static final int SQLITE_IOERR_FSTAT = 1802; + public static final int SQLITE_IOERR_UNLOCK = 2058; + public static final int SQLITE_IOERR_RDLOCK = 2314; + public static final int SQLITE_IOERR_DELETE = 2570; + public static final int SQLITE_IOERR_BLOCKED = 2826; + public static final int SQLITE_IOERR_NOMEM = 3082; + public static final int SQLITE_IOERR_ACCESS = 3338; + public static final int SQLITE_IOERR_CHECKRESERVEDLOCK = 3594; + public static final int SQLITE_IOERR_LOCK = 3850; + public static final int SQLITE_IOERR_CLOSE = 4106; + public static final int SQLITE_IOERR_DIR_CLOSE = 4362; + public static final int SQLITE_IOERR_SHMOPEN = 4618; + public static final int SQLITE_IOERR_SHMSIZE = 4874; + public static final int SQLITE_IOERR_SHMLOCK = 5130; + public static final int SQLITE_IOERR_SHMMAP = 5386; + public static final int SQLITE_IOERR_SEEK = 5642; + public static final int SQLITE_IOERR_DELETE_NOENT = 5898; + public static final int SQLITE_IOERR_MMAP = 6154; + public static final int SQLITE_IOERR_GETTEMPPATH = 6410; + public static final int SQLITE_IOERR_CONVPATH = 6666; + public static final int SQLITE_IOERR_VNODE = 6922; + public static final int SQLITE_IOERR_AUTH = 7178; + public static final int SQLITE_IOERR_BEGIN_ATOMIC = 7434; + public static final int SQLITE_IOERR_COMMIT_ATOMIC = 7690; + public static final int SQLITE_IOERR_ROLLBACK_ATOMIC = 7946; + public static final int SQLITE_IOERR_DATA = 8202; + public static final int SQLITE_IOERR_CORRUPTFS = 8458; + public static final int SQLITE_LOCKED_SHAREDCACHE = 262; + public static final int SQLITE_LOCKED_VTAB = 518; + public static final int SQLITE_BUSY_RECOVERY = 261; + public static final int SQLITE_BUSY_SNAPSHOT = 517; + public static final int SQLITE_BUSY_TIMEOUT = 773; + public static final int SQLITE_CANTOPEN_NOTEMPDIR = 270; + public static final int SQLITE_CANTOPEN_ISDIR = 526; + public static final int SQLITE_CANTOPEN_FULLPATH = 782; + public static final int SQLITE_CANTOPEN_CONVPATH = 1038; + public static final int SQLITE_CANTOPEN_SYMLINK = 1550; + public static final int SQLITE_CORRUPT_VTAB = 267; + public static final int SQLITE_CORRUPT_SEQUENCE = 523; + public static final int SQLITE_CORRUPT_INDEX = 779; + public static final int SQLITE_READONLY_RECOVERY = 264; + public static final int SQLITE_READONLY_CANTLOCK = 520; + public static final int SQLITE_READONLY_ROLLBACK = 776; + public static final int SQLITE_READONLY_DBMOVED = 1032; + public static final int SQLITE_READONLY_CANTINIT = 1288; + public static final int SQLITE_READONLY_DIRECTORY = 1544; + public static final int SQLITE_ABORT_ROLLBACK = 516; + public static final int SQLITE_CONSTRAINT_CHECK = 275; + public static final int SQLITE_CONSTRAINT_COMMITHOOK = 531; + public static final int SQLITE_CONSTRAINT_FOREIGNKEY = 787; + public static final int SQLITE_CONSTRAINT_FUNCTION = 1043; + public static final int SQLITE_CONSTRAINT_NOTNULL = 1299; + public static final int SQLITE_CONSTRAINT_PRIMARYKEY = 1555; + public static final int SQLITE_CONSTRAINT_TRIGGER = 1811; + public static final int SQLITE_CONSTRAINT_UNIQUE = 2067; + public static final int SQLITE_CONSTRAINT_VTAB = 2323; + public static final int SQLITE_CONSTRAINT_ROWID = 2579; + public static final int SQLITE_CONSTRAINT_PINNED = 2835; + public static final int SQLITE_CONSTRAINT_DATATYPE = 3091; + public static final int SQLITE_NOTICE_RECOVER_WAL = 283; + public static final int SQLITE_NOTICE_RECOVER_ROLLBACK = 539; + public static final int SQLITE_WARNING_AUTOINDEX = 284; + public static final int SQLITE_AUTH_USER = 279; + public static final int SQLITE_OK_LOAD_PERMANENTLY = 256; + + // serialize + public static final int SQLITE_SERIALIZE_NOCOPY = 1; + public static final int SQLITE_DESERIALIZE_FREEONCLOSE = 1; + public static final int SQLITE_DESERIALIZE_READONLY = 4; + public static final int SQLITE_DESERIALIZE_RESIZEABLE = 2; + + // session + public static final int SQLITE_SESSION_CONFIG_STRMSIZE = 1; + public static final int SQLITE_SESSION_OBJCONFIG_SIZE = 1; + + // sqlite3 status + public static final int SQLITE_STATUS_MEMORY_USED = 0; + public static final int SQLITE_STATUS_PAGECACHE_USED = 1; + public static final int SQLITE_STATUS_PAGECACHE_OVERFLOW = 2; + public static final int SQLITE_STATUS_MALLOC_SIZE = 5; + public static final int SQLITE_STATUS_PARSER_STACK = 6; + public static final int SQLITE_STATUS_PAGECACHE_SIZE = 7; + public static final int SQLITE_STATUS_MALLOC_COUNT = 9; + + // stmt status + public static final int SQLITE_STMTSTATUS_FULLSCAN_STEP = 1; + public static final int SQLITE_STMTSTATUS_SORT = 2; + public static final int SQLITE_STMTSTATUS_AUTOINDEX = 3; + public static final int SQLITE_STMTSTATUS_VM_STEP = 4; + public static final int SQLITE_STMTSTATUS_REPREPARE = 5; + public static final int SQLITE_STMTSTATUS_RUN = 6; + public static final int SQLITE_STMTSTATUS_FILTER_MISS = 7; + public static final int SQLITE_STMTSTATUS_FILTER_HIT = 8; + public static final int SQLITE_STMTSTATUS_MEMUSED = 99; + + // sync flags + public static final int SQLITE_SYNC_NORMAL = 2; + public static final int SQLITE_SYNC_FULL = 3; + public static final int SQLITE_SYNC_DATAONLY = 16; + + // tracing flags + public static final int SQLITE_TRACE_STMT = 1; + public static final int SQLITE_TRACE_PROFILE = 2; + public static final int SQLITE_TRACE_ROW = 4; + public static final int SQLITE_TRACE_CLOSE = 8; + + // transaction state + public static final int SQLITE_TXN_NONE = 0; + public static final int SQLITE_TXN_READ = 1; + public static final int SQLITE_TXN_WRITE = 2; + + // udf flags + public static final int SQLITE_DETERMINISTIC = 2048; + public static final int SQLITE_DIRECTONLY = 524288; + public static final int SQLITE_INNOCUOUS = 2097152; + + // virtual tables + public static final int SQLITE_INDEX_SCAN_UNIQUE = 1; + public static final int SQLITE_INDEX_CONSTRAINT_EQ = 2; + public static final int SQLITE_INDEX_CONSTRAINT_GT = 4; + public static final int SQLITE_INDEX_CONSTRAINT_LE = 8; + public static final int SQLITE_INDEX_CONSTRAINT_LT = 16; + public static final int SQLITE_INDEX_CONSTRAINT_GE = 32; + public static final int SQLITE_INDEX_CONSTRAINT_MATCH = 64; + public static final int SQLITE_INDEX_CONSTRAINT_LIKE = 65; + public static final int SQLITE_INDEX_CONSTRAINT_GLOB = 66; + public static final int SQLITE_INDEX_CONSTRAINT_REGEXP = 67; + public static final int SQLITE_INDEX_CONSTRAINT_NE = 68; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOT = 69; + public static final int SQLITE_INDEX_CONSTRAINT_ISNOTNULL = 70; + public static final int SQLITE_INDEX_CONSTRAINT_ISNULL = 71; + public static final int SQLITE_INDEX_CONSTRAINT_IS = 72; + public static final int SQLITE_INDEX_CONSTRAINT_LIMIT = 73; + public static final int SQLITE_INDEX_CONSTRAINT_OFFSET = 74; + public static final int SQLITE_INDEX_CONSTRAINT_FUNCTION = 150; + public static final int SQLITE_VTAB_CONSTRAINT_SUPPORT = 1; + public static final int SQLITE_VTAB_INNOCUOUS = 2; + public static final int SQLITE_VTAB_DIRECTONLY = 3; + public static final int SQLITE_VTAB_USES_ALL_SCHEMAS = 4; + public static final int SQLITE_ROLLBACK = 1; + public static final int SQLITE_FAIL = 3; + public static final int SQLITE_REPLACE = 5; + static { + // This MUST come after the SQLITE_MAX_... values or else + // attempting to modify them silently fails. + init(new SQLite3Jni()); + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java new file mode 100644 index 0000000000..910fc85005 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -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 void out(T val){ + System.out.print(val); + } + + private static 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 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 xDestroyCalled = new ValueHolder<>(false); + final ValueHolder 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 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 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 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 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 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."); + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tracer.java b/ext/jni/src/org/sqlite/jni/Tracer.java new file mode 100644 index 0000000000..52ccfabafc --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Tracer.java @@ -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); +} diff --git a/ext/jni/src/org/sqlite/jni/ValueHolder.java b/ext/jni/src/org/sqlite/jni/ValueHolder.java new file mode 100644 index 0000000000..7f6a463ba5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ValueHolder.java @@ -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 { + public T value; + public ValueHolder(){} + public ValueHolder(T v){value = v;} +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java new file mode 100644 index 0000000000..82bb559117 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -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 { + 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); + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java new file mode 100644 index 0000000000..9d053ffe9a --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -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 { + public sqlite3_context() { + super(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java new file mode 100644 index 0000000000..199689a8bd --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java @@ -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 { + 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); + } +} diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/sqlite3_value.java new file mode 100644 index 0000000000..41347d8785 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/sqlite3_value.java @@ -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 { + public sqlite3_value() { + super(); + } +} diff --git a/manifest b/manifest index 9173c248a5..21efb214a7 100644 --- a/manifest +++ b/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. diff --git a/manifest.uuid b/manifest.uuid index 3150e0134d..8421cad165 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d66b182d2bc6ce0772e69401b7affe1adbc1b128c4631cb3c17f98dde72af00a \ No newline at end of file +b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5 \ No newline at end of file From fd250c2cb57767095ddea00f807f14232cd8526b Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 27 Jul 2023 20:32:16 +0000 Subject: [PATCH 002/148] Replace some www: interwiki references in the JNI readme with their full URLs to make the doc more portable. FossilOrigin-Name: 63ce0c9bdde210cf2f8b6099ae5c73caac18e6debc13c2f77090b77f3de72beb --- ext/jni/README.md | 8 ++++---- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index 0adf38527d..4cb76705f3 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -4,7 +4,7 @@ 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 +> **FOREWARNING:** this subproject 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. @@ -12,7 +12,7 @@ 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) + goal is that [the C documentation](https://sqlite.org/c3ref/intro.html) should be usable as-is, insofar as possible, for the JNI binding. - Support Java as far back as version 8 (2014). @@ -107,7 +107,7 @@ demonstration of where such changes are "really necessary"... A prime example of where interface changes for Java are necessary for usability is [registration of a custom -collation](www:/c3ref/create_collation.html): +collation](https://sqlite.org/c3ref/create_collation.html): ``` // C: @@ -183,7 +183,7 @@ Noting that: ### User-defined SQL Functions (a.k.a. UDFs) -The [`sqlite3_create_function()`](www:/c3ref/create_function.html) +The [`sqlite3_create_function()`](https://sqlite.org/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: diff --git a/manifest b/manifest index 28272160aa..393405cc0d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\strunk\sinto\sjni\sbranch\sfor\scompiler\swarning\sfixes. -D 2023-07-27T20:12:48.495 +C Replace\ssome\swww:\sinterwiki\sreferences\sin\sthe\sJNI\sreadme\swith\stheir\sfull\sURLs\sto\smake\sthe\sdoc\smore\sportable. +D 2023-07-27T20:32:16.722 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 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/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 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 @@ -2066,8 +2066,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b5374b9ef58fa0be80aefccde0721f5599fb820464b13940b6361b9aa09a59d5 bf71faa2a1d29ea762c4d2485522d6f4f8a5a7166981a92d3ba9c96ccbbe1213 -R 7dd86993b43278424be52f48d5e5f4e4 +P 0514fd340ae15a95760d50c747d6fb9eae5109cb5045eeabc2bc199be0a5ae35 +R fbc3a722616dae826b754c2990ee20ad U stephan -Z 1238bb2bb995ce156bfcc94eb1fcf862 +Z 82e6c4339ed3624b75f36686218f69e5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2724037502..90c4a09c65 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0514fd340ae15a95760d50c747d6fb9eae5109cb5045eeabc2bc199be0a5ae35 \ No newline at end of file +63ce0c9bdde210cf2f8b6099ae5c73caac18e6debc13c2f77090b77f3de72beb \ No newline at end of file From 1a95091b49738141022022aded69a305d5e309ec Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 27 Jul 2023 22:05:39 +0000 Subject: [PATCH 003/148] Bind sqlite3_busy_handler(). Correct mapping of pointers for, and cleanup of, JNI-level per-db state. FossilOrigin-Name: 524747796a30a5c1c6c7567b49ffb1e35e2626c73e09c335c0ab74d4ddb5f005 --- ext/jni/src/c/sqlite3-jni.c | 191 ++++++++++++++++---- ext/jni/src/c/sqlite3-jni.h | 8 + ext/jni/src/org/sqlite/jni/BusyHandler.java | 42 +++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 9 +- ext/jni/src/org/sqlite/jni/Tester1.java | 23 +++ manifest | 19 +- manifest.uuid | 2 +- 7 files changed, 245 insertions(+), 49 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/BusyHandler.java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index ba892afc56..581300c9d5 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -316,6 +316,17 @@ static void JNIEnvCache_clear(JNIEnvCache * p){ memset(p, 0, sizeof(JNIEnvCache)); } +/** + State for binding Java-side busy handlers. +*/ +typedef struct { + JNIEnv * env; /* env registered from */; + jobject jObj /* BusyHandlerJni instance */; + jclass klazz /* jObj's class */; + jmethodID jmidxCallback; +} BusyHandlerJni; + + /** Per-(sqlite3*) state for bindings which do not have their own finalizer functions, e.g. tracing and commit/rollback hooks. This @@ -328,12 +339,12 @@ static void JNIEnvCache_clear(JNIEnvCache * p){ data (since they can(?) hypothetically be set via multiple JNIEnv objects)? */ -typedef struct PerDbState PerDbState; -struct PerDbState { +typedef struct PerDbStateJni PerDbStateJni; +struct PerDbStateJni { JNIEnv *env; sqlite3 * pDb; - PerDbState * pNext; - PerDbState * pPrev; + PerDbStateJni * pNext; + PerDbStateJni * pPrev; struct { jobject jObj; jmethodID midCallback; @@ -350,6 +361,7 @@ struct PerDbState { jobject jObj; jmethodID midCallback; } rollbackHook; + BusyHandlerJni busyHandler; }; static struct { @@ -367,8 +379,8 @@ static struct { JavaVM * jvm; struct JNIEnvCache envCache; struct { - PerDbState * aUsed; - PerDbState * aFree; + PerDbStateJni * aUsed; + PerDbStateJni * aFree; } perDb; } S3Global; @@ -389,13 +401,57 @@ static void * s3jni_malloc(JNIEnv *env, size_t n){ static void s3jni_free(void * p){ if(p) sqlite3_free(p); } + /** - Extracts the new PerDbState instance from the free-list, or + Clears s's state, releasing any Java references. Before doing so, + it calls s's xDestroy() method, ignoring the lack of that method or + any exceptions it throws. This is a no-op of s has no current + state. +*/ +static void BusyHandlerJni_clear(BusyHandlerJni * const s){ + if(s->jObj){ + JNIEnv * const env = s->env; + const jmethodID method = + (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); + if(method){ + (*env)->CallVoidMethod(env, s->jObj, method); + EXCEPTION_IGNORE; + }else{ + EXCEPTION_CLEAR; + } + UNREF_G(s->jObj); + UNREF_G(s->klazz); + memset(s, 0, sizeof(BusyHandlerJni)); + } +} + +/** + Initializes s to wrap BusyHandlerJni-type object jObject, clearning + any current state of s beforehand. Returns 0 on success, non-0 on + error. On error, s's state is cleared. +*/ +static int BusyHandlerJni_init(JNIEnv * const env, BusyHandlerJni * const s, + jobject jObj){ + const char * zSig = "(I)I" /* callback signature */; + if(s->jObj) BusyHandlerJni_clear(s); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); + s->jmidxCallback = (*env)->GetMethodID(env, s->klazz, "xCallback", zSig); + IFTHREW { + BusyHandlerJni_clear(s); + return SQLITE_ERROR; + } + return 0; +} + +/** + Extracts the new PerDbStateJni instance from the free-list, or allocates one if needed, associats it with pDb, and returns. Returns NULL on OOM. */ -static PerDbState * PerDbState_alloc(JNIEnv *env, sqlite3 *pDb){ - PerDbState * rv; +static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb){ + PerDbStateJni * rv; assert( pDb ); if(S3Global.perDb.aFree){ rv = S3Global.perDb.aFree; @@ -407,18 +463,18 @@ static PerDbState * PerDbState_alloc(JNIEnv *env, sqlite3 *pDb){ rv->pNext = 0; } }else{ - rv = s3jni_malloc(env, sizeof(PerDbState)); + rv = s3jni_malloc(env, sizeof(PerDbStateJni)); if(rv){ - memset(rv, 0, sizeof(PerDbState)); - rv->pNext = S3Global.perDb.aUsed; - S3Global.perDb.aUsed = rv; - if(rv->pNext){ - assert(!rv->pNext->pPrev); - rv->pNext->pPrev = rv; - } + memset(rv, 0, sizeof(PerDbStateJni)); } } if(rv){ + rv->pNext = S3Global.perDb.aUsed; + S3Global.perDb.aUsed = rv; + if(rv->pNext){ + assert(!rv->pNext->pPrev); + rv->pNext->pPrev = rv; + } rv->pDb = pDb; rv->env = env; } @@ -429,7 +485,7 @@ static PerDbState * PerDbState_alloc(JNIEnv *env, sqlite3 *pDb){ Clears s's state and moves it to the free-list. */ FIXME_THREADING -static void PerDbState_set_aside(PerDbState *s){ +static void PerDbStateJni_set_aside(PerDbStateJni *s){ if(s){ JNIEnv * const env = s->env; assert(s->pDb && "Else this object is already in the free-list."); @@ -443,27 +499,38 @@ static void PerDbState_set_aside(PerDbState *s){ UNREF_G(s->progress.jObj); UNREF_G(s->commitHook.jObj); UNREF_G(s->rollbackHook.jObj); - s->env = 0; - s->pDb = 0; - s->pPrev = 0; + BusyHandlerJni_clear(&s->busyHandler); + memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; S3Global.perDb.aFree = s; } } +static void PerDbStateJni_dump(PerDbStateJni *s){ + MARKER(("PerDbStateJni->env @ %p\n", s->env)); + MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb)); + MARKER(("PerDbStateJni->trace.jObj @ %p\n", s->trace.jObj)); + MARKER(("PerDbStateJni->progress.jObj @ %p\n", s->progress.jObj)); + MARKER(("PerDbStateJni->commitHook.jObj @ %p\n", s->commitHook.jObj)); + MARKER(("PerDbStateJni->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); + MARKER(("PerDbStateJni->busyHandler.env @ %p\n", s->busyHandler.env)); + MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); + MARKER(("PerDbStateJni->env @ %p\n", s->env)); +} + /** - Returns the PerDbState object for the given db. If allocIfNeeded is + Returns the PerDbStateJni object for the given db. If allocIfNeeded is true then a new instance will be allocated if no mapping currently exists, else NULL is returned if no mapping is found. */ FIXME_THREADING -static PerDbState * PerDbState_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeeded){ - PerDbState * s = S3Global.perDb.aUsed; +static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeeded){ + PerDbStateJni * s = S3Global.perDb.aUsed; for( ; s; s = s->pNext){ if(s->pDb == pDb) return s; } - if(allocIfNeeded) s = PerDbState_alloc(env, pDb); + if(allocIfNeeded) s = PerDbStateJni_alloc(env, pDb); return s; } @@ -471,12 +538,12 @@ static PerDbState * PerDbState_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeed Cleans up and frees all state in S3Global.perDb. */ FIXME_THREADING -static void PerDbState_free_all(void){ - PerDbState * pS = S3Global.perDb.aUsed; - PerDbState * pSNext = 0; +static void PerDbStateJni_free_all(void){ + PerDbStateJni * pS = S3Global.perDb.aUsed; + PerDbStateJni * pSNext = 0; for( ; pS; pS = pSNext ){ pSNext = pS->pNext; - PerDbState_set_aside(pS); + PerDbStateJni_set_aside(pS); assert(pSNext ? !pSNext->pPrev : 1); } assert( 0==S3Global.perDb.aUsed ); @@ -1205,8 +1272,50 @@ JDECL(jint,1bind_1zeroblob64)(JENV_JSELF, jobject jpStmt, return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); } -JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject pDb, jint ms){ - return sqlite3_busy_timeout(PtrGet_sqlite3(pDb), (int)ms); +static int s3jni_busy_handler(void* pState, int n){ + PerDbStateJni * const pS = (PerDbStateJni *)pState; + int rc = 0; + if( pS->busyHandler.jObj ){ + JNIEnv * const env = pS->env; + rc = (*env)->CallIntMethod(env, pS->busyHandler.jObj, + pS->busyHandler.jmidxCallback, (jint)n); + } + return rc; +} + +JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + PerDbStateJni * const pS = PerDbStateJni_for_db(env, pDb, 1); + int rc; + if(!pS) return (jint)SQLITE_NOMEM; + if(jBusy){ + if(pS->busyHandler.jObj && + (*env)->IsSameObject(env, pS->busyHandler.jObj, jBusy)){ + /* Same object - this is a no-op. */ + return 0; + } + rc = BusyHandlerJni_init(env, &pS->busyHandler, jBusy); + if(rc){ + assert(!pS->busyHandler.jObj); + return (jint)rc; + } + assert(pS->busyHandler.jObj && pS->busyHandler.klazz); + assert( (*env)->IsSameObject(env, pS->busyHandler.jObj, jBusy) ); + }else{ + BusyHandlerJni_clear(&pS->busyHandler); + } + return jBusy + ? sqlite3_busy_handler(pDb, s3jni_busy_handler, pS) + : sqlite3_busy_handler(pDb, 0, 0); +} + +JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ + sqlite3* const pDb = PtrGet_sqlite3(jDb); + PerDbStateJni * const pS = PerDbStateJni_for_db(env, pDb, 0); + if( pS && pS->busyHandler.jObj ){ + BusyHandlerJni_clear(&pS->busyHandler); + } + return sqlite3_busy_timeout(pDb, (int)ms); } /** @@ -1215,13 +1324,19 @@ JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject pDb, jint ms){ static jint s3jni_close_db(JNIEnv *env, jobject jDb, int version){ sqlite3 * pDb; int rc = 0; - PerDbState * pS; + PerDbStateJni * pS = 0; assert(version == 1 || version == 2); + if(0){ + PerDbStateJni * s = S3Global.perDb.aUsed; + for( ; s; s = s->pNext){ + PerDbStateJni_dump(s); + } + } pDb = PtrGet_sqlite3(jDb); if(!pDb) return rc; - pS = PerDbState_for_db(env, pDb, 0); + pS = PerDbStateJni_for_db(env, pDb, 0); rc = 1==version ? (jint)sqlite3_close(pDb) : (jint)sqlite3_close_v2(pDb); - if(pS) PerDbState_set_aside(pS) + if(pS) PerDbStateJni_set_aside(pS) /* MUST come after close() because of pS->trace. */; setNativePointer(env, jDb, 0, ClassNames.sqlite3); return (jint)rc; @@ -1706,7 +1821,7 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ } JDECL(jint,1shutdown)(JENV_JSELF){ - PerDbState_free_all(); + PerDbStateJni_free_all(); JNIEnvCache_clear(&S3Global.envCache); /* Do not clear S3Global.jvm: it's legal to call sqlite3_initialize() again to restart the lib. */ @@ -1714,7 +1829,7 @@ JDECL(jint,1shutdown)(JENV_JSELF){ } static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ - PerDbState * const ps = (PerDbState *)pC; + PerDbStateJni * const ps = (PerDbStateJni *)pC; JNIEnv * const env = ps->env; jobject jX = NULL; JNIEnvCacheLine * const pEcl = S3Global_env_cache(env); @@ -1752,12 +1867,12 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbState * ps; + PerDbStateJni * ps; jclass klazz; if( !traceMask || !jTracer ){ return (jint)sqlite3_trace_v2(pDb, 0, 0, 0); } - ps = PerDbState_for_db(env, pDb, 1); + ps = PerDbStateJni_for_db(env, pDb, 1); if(!ps) return SQLITE_NOMEM; klazz = (*env)->GetObjectClass(env, jTracer); ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index a55d437152..524a9b4075 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -843,6 +843,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1bind_1zeroblob64 (JNIEnv *, jclass, jobject, jint, jlong); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_busy_handler + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/BusyHandler;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_busy_timeout diff --git a/ext/jni/src/org/sqlite/jni/BusyHandler.java b/ext/jni/src/org/sqlite/jni/BusyHandler.java new file mode 100644 index 0000000000..d3a3675f06 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/BusyHandler.java @@ -0,0 +1,42 @@ +/* +** 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_busy_handler(). +*/ +public abstract class BusyHandler { + /** + Must function as documented for the sqlite3_busy_handler() + callback argument, minus the (void*) argument the C-level + function requires. + */ + public abstract int xCallback(int n); + + /** + Optionally override to perform any cleanup when this busy + handler is destroyed. It is destroyed when: + + - The associated db is passed to sqlite3_close() or + sqlite3_close_v2(). + + - sqlite3_busy_handler() is called to replace the handler, + whether it's passed a null handler or any other instance of + this class. + + - sqlite3_busy_timeout() is called, which implicitly installs + a busy handler. + */ + public void xDestroy(){} +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 53b920f808..b0ae33c640 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -133,7 +133,14 @@ public final class SQLite3Jni { public static native int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n); - //TODO? public static native int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); + /** + As for the C-level function of the same name, with a BusyHandler + instance in place of a callback function. Pass it a null handler + to clear the busy handler. Calling this multiple times with the + same object is a no-op on the second and subsequent calls. + */ + public static native int sqlite3_busy_handler(@NotNull sqlite3 db, + @Nullable BusyHandler handler); public static native int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 910fc85005..b4c3f97e8c 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -640,6 +640,28 @@ public class Tester1 { myassert( 7 == counter.value ); } + private static void testBusy(){ + outln("testBusy()..."); + final sqlite3 db = createNewDb(); + final ValueHolder xDestroyed = new ValueHolder<>(false); + BusyHandler handler = new BusyHandler(){ + @Override public int xCallback(int n){ + /* How do we conveniently test this? */ + return 0; + } + @Override public void xDestroy(){ + xDestroyed.value = true; + } + }; + outln("setting busy handler..."); + int rc = sqlite3_busy_handler(db, handler); + outln("set busy handler"); + myassert(0 == rc); + myassert( false == xDestroyed.value ); + sqlite3_close_v2(db); + myassert( true == xDestroyed.value ); + } + private static void testMisc(){ outln("Sleeping..."); sqlite3_sleep(500); @@ -666,6 +688,7 @@ public class Tester1 { testUdfAggregate(); testUdfWindow(); testTrace(); + testBusy(); testMisc(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); diff --git a/manifest b/manifest index 393405cc0d..90de8b5efc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Replace\ssome\swww:\sinterwiki\sreferences\sin\sthe\sJNI\sreadme\swith\stheir\sfull\sURLs\sto\smake\sthe\sdoc\smore\sportable. -D 2023-07-27T20:32:16.722 +C Bind\ssqlite3_busy_handler().\sCorrect\smapping\sof\spointers\sfor,\sand\scleanup\sof,\sJNI-level\sper-db\sstate. +D 2023-07-27T22:05:39.096 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,15 +232,16 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 -F ext/jni/src/c/sqlite3-jni.c 55bf5624beee849b1c063bf929e6066dc95437564c3212d30e672280bec45da8 -F ext/jni/src/c/sqlite3-jni.h ef862321bb153135472ebe6be6df9db3e47448ae3ef6bb3cb7953c54971efcf8 +F ext/jni/src/c/sqlite3-jni.c 8274a016b5049651ca016486639f90abf050020cf6c880b5273f4e15b29d1ffc +F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 +F ext/jni/src/org/sqlite/jni/BusyHandler.java aa7574dcf08500ab2334a0ce09c24593374db89815d76fee16da09641c1e32ce 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/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 +F ext/jni/src/org/sqlite/jni/Tester1.java 1b5f638c9efa0a18579fca5172f4514cd73c8202eacf002fbcc2726efe67fcf0 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 @@ -2066,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0514fd340ae15a95760d50c747d6fb9eae5109cb5045eeabc2bc199be0a5ae35 -R fbc3a722616dae826b754c2990ee20ad +P 63ce0c9bdde210cf2f8b6099ae5c73caac18e6debc13c2f77090b77f3de72beb +R c101ee50ca81ac0c17b72552c1b109f1 U stephan -Z 82e6c4339ed3624b75f36686218f69e5 +Z 3865f2d2a0d46095d9527e301bfc90cc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 90c4a09c65..a432b84e8f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -63ce0c9bdde210cf2f8b6099ae5c73caac18e6debc13c2f77090b77f3de72beb \ No newline at end of file +524747796a30a5c1c6c7567b49ffb1e35e2626c73e09c335c0ab74d4ddb5f005 \ No newline at end of file From fd022a23a5dc94bfc066f63b338154b523faf842 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 27 Jul 2023 22:42:25 +0000 Subject: [PATCH 004/148] Add locking test for BusyHandler and clear up new -Xlint:jni warnings introduced by the busy-handler binding. FossilOrigin-Name: 7dcde2bfce54b18f391776fa1cb93c0ff6153634bedcab0007b374c06c4d4079 --- ext/jni/src/c/sqlite3-jni.c | 8 +++-- ext/jni/src/org/sqlite/jni/BusyHandler.java | 3 ++ ext/jni/src/org/sqlite/jni/Tester1.java | 38 +++++++++++++++++---- manifest | 16 ++++----- manifest.uuid | 2 +- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 581300c9d5..374518ac34 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -154,6 +154,7 @@ #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) #define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) #define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT +#define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR #define PtrGet_sqlite3(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3) @@ -415,7 +416,7 @@ static void BusyHandlerJni_clear(BusyHandlerJni * const s){ (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); if(method){ (*env)->CallVoidMethod(env, s->jObj, method); - EXCEPTION_IGNORE; + IFTHREW_CLEAR; }else{ EXCEPTION_CLEAR; } @@ -1279,6 +1280,7 @@ static int s3jni_busy_handler(void* pState, int n){ JNIEnv * const env = pS->env; rc = (*env)->CallIntMethod(env, pS->busyHandler.jObj, pS->busyHandler.jmidxCallback, (jint)n); + IFTHREW_CLEAR; } return rc; } @@ -1550,10 +1552,10 @@ JDECL(jint,1initialize)(JENV_JSELF){ } JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ - if(jpStmt){ + if( jpStmt ){ sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt); setNativePointer(env, jpStmt, 0, ClassNames.sqlite3_stmt); - sqlite3_finalize(pStmt); + if( pStmt ) sqlite3_finalize(pStmt); } return 0; } diff --git a/ext/jni/src/org/sqlite/jni/BusyHandler.java b/ext/jni/src/org/sqlite/jni/BusyHandler.java index d3a3675f06..8ce729c904 100644 --- a/ext/jni/src/org/sqlite/jni/BusyHandler.java +++ b/ext/jni/src/org/sqlite/jni/BusyHandler.java @@ -21,6 +21,9 @@ public abstract class BusyHandler { Must function as documented for the sqlite3_busy_handler() callback argument, minus the (void*) argument the C-level function requires. + + Any exceptions thrown by this callback are suppressed in order to + retain the C-style API semantics of the JNI bindings. */ public abstract int xCallback(int n); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index b4c3f97e8c..49c0953184 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -642,24 +642,48 @@ public class Tester1 { private static void testBusy(){ outln("testBusy()..."); - final sqlite3 db = createNewDb(); + final String dbName = "_busy-handler.db"; + final sqlite3 db1 = new sqlite3(); + final sqlite3 db2 = new sqlite3(); + + int rc = sqlite3_open(dbName, db1); + myassert( 0 == rc ); + execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); + rc = sqlite3_open(dbName, db2); + myassert( 0 == rc ); + final ValueHolder xDestroyed = new ValueHolder<>(false); + final ValueHolder xBusyCalled = new ValueHolder<>(0); BusyHandler handler = new BusyHandler(){ @Override public int xCallback(int n){ - /* How do we conveniently test this? */ - return 0; + //outln("busy handler #"+n); + return n > 2 ? 0 : ++xBusyCalled.value; } @Override public void xDestroy(){ xDestroyed.value = true; } }; - outln("setting busy handler..."); - int rc = sqlite3_busy_handler(db, handler); - outln("set busy handler"); + rc = sqlite3_busy_handler(db2, handler); myassert(0 == rc); + + // Force a locked condition... + execSql(db1, "BEGIN EXCLUSIVE"); myassert( false == xDestroyed.value ); - sqlite3_close_v2(db); + sqlite3_stmt stmt = new sqlite3_stmt(); + rc = sqlite3_prepare(db2, "SELECT * from t", stmt); + myassert( SQLITE_BUSY == rc); + myassert( 3 == xBusyCalled.value ); + sqlite3_finalize(stmt); + sqlite3_close(db1); + myassert( false == xDestroyed.value ); + sqlite3_close(db2); myassert( true == xDestroyed.value ); + try{ + final java.io.File f = new java.io.File(dbName); + f.delete(); + }catch(Exception e){ + /* ignore */ + } } private static void testMisc(){ diff --git a/manifest b/manifest index 90de8b5efc..45a4ee9515 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_busy_handler().\sCorrect\smapping\sof\spointers\sfor,\sand\scleanup\sof,\sJNI-level\sper-db\sstate. -D 2023-07-27T22:05:39.096 +C Add\slocking\stest\sfor\sBusyHandler\sand\sclear\sup\snew\s-Xlint:jni\swarnings\sintroduced\sby\sthe\sbusy-handler\sbinding. +D 2023-07-27T22:42:25.017 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,16 +232,16 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 -F ext/jni/src/c/sqlite3-jni.c 8274a016b5049651ca016486639f90abf050020cf6c880b5273f4e15b29d1ffc +F ext/jni/src/c/sqlite3-jni.c 76921edc2d1abea2cb39c21bcc49acbc307cb368e96cb7803a2c134c444c3fcd F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 -F ext/jni/src/org/sqlite/jni/BusyHandler.java aa7574dcf08500ab2334a0ce09c24593374db89815d76fee16da09641c1e32ce +F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c 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 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 -F ext/jni/src/org/sqlite/jni/Tester1.java 1b5f638c9efa0a18579fca5172f4514cd73c8202eacf002fbcc2726efe67fcf0 +F ext/jni/src/org/sqlite/jni/Tester1.java 7a5cc47aabca372a3bc9d078bdd90e10d00d517c3ebff19d023fd74272725ed9 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 63ce0c9bdde210cf2f8b6099ae5c73caac18e6debc13c2f77090b77f3de72beb -R c101ee50ca81ac0c17b72552c1b109f1 +P 524747796a30a5c1c6c7567b49ffb1e35e2626c73e09c335c0ab74d4ddb5f005 +R 3c532f44a6a487cc227202d8226f03e9 U stephan -Z 3865f2d2a0d46095d9527e301bfc90cc +Z d6942649a24407ffbbac534429e75b47 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a432b84e8f..ab1bac8415 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -524747796a30a5c1c6c7567b49ffb1e35e2626c73e09c335c0ab74d4ddb5f005 \ No newline at end of file +7dcde2bfce54b18f391776fa1cb93c0ff6153634bedcab0007b374c06c4d4079 \ No newline at end of file From 8ba5d79c35805e6f078bcd58bd482fcd58b213b2 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 27 Jul 2023 22:53:02 +0000 Subject: [PATCH 005/148] Reformulate jni tests to not require the -ea jvm flag to enable assert(). FossilOrigin-Name: dc356667a8f4fa31a3fef1ae35873d834d27fd6a9f0818d6fb85e4751fde9fe5 --- ext/jni/src/org/sqlite/jni/Tester1.java | 256 ++++++++++++------------ manifest | 12 +- manifest.uuid | 2 +- 3 files changed, 134 insertions(+), 136 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 49c0953184..4b760661db 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -10,8 +10,6 @@ ** ************************************************************************* ** 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.*; @@ -28,10 +26,10 @@ public class Tester1 { System.out.println(val); } - private static int assertCount = 0; - private static void myassert(Boolean v){ - ++assertCount; - assert( v ); + private static int affirmCount = 0; + private static void affirm(Boolean v){ + ++affirmCount; + if( !v ) throw new RuntimeException("Assertion failed."); } private static void test1(){ @@ -41,10 +39,10 @@ public class Tester1 { + sqlite3_libversion() + "\n" + SQLITE_SOURCE_ID); - myassert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); + affirm(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER); //outln("threadsafe = "+sqlite3_threadsafe()); - myassert(SQLITE_MAX_LENGTH > 0); - myassert(SQLITE_MAX_TRIGGER_DEPTH>0); + affirm(SQLITE_MAX_LENGTH > 0); + affirm(SQLITE_MAX_TRIGGER_DEPTH>0); } private static void testCompileOption(){ @@ -74,12 +72,12 @@ public class Tester1 { } if( 0==sqlChunk.length ) break; int rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); - myassert(0 == rc); + affirm(0 == rc); pos = oTail.getValue(); - myassert(0 != stmt.getNativePointer()); + affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); sqlite3_finalize(stmt); - myassert(0 == stmt.getNativePointer()); + affirm(0 == stmt.getNativePointer()); if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ throw new RuntimeException("db op failed with rc="+rc); } @@ -87,34 +85,34 @@ public class Tester1 { } private static void testOpenDb1(){ sqlite3 db = new sqlite3(); - myassert(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); int rc = sqlite3_open(":memory:", db); - myassert(0 == rc); - myassert(0 < db.getNativePointer()); + affirm(0 == rc); + affirm(0 < db.getNativePointer()); sqlite3_close(db); - myassert(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); } private static void testOpenDb2(){ sqlite3 db = new sqlite3(); - myassert(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); int rc = sqlite3_open_v2(":memory:", db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null); - myassert(0 == rc); - myassert(0 < db.getNativePointer()); + affirm(0 == rc); + affirm(0 < db.getNativePointer()); sqlite3_close_v2(db); - myassert(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); } private static sqlite3 createNewDb(){ sqlite3 db = new sqlite3(); - myassert(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); int rc = sqlite3_open(":memory:", db); - myassert(0 == rc); - myassert(0 != db.getNativePointer()); + affirm(0 == rc); + affirm(0 != db.getNativePointer()); rc = sqlite3_busy_timeout(db, 2000); - myassert( 0 == rc ); + affirm( 0 == rc ); return db; } @@ -122,14 +120,14 @@ public class Tester1 { sqlite3 db = createNewDb(); int rc; sqlite3_stmt stmt = new sqlite3_stmt(); - myassert(0 == stmt.getNativePointer()); + affirm(0 == stmt.getNativePointer()); rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", stmt); - myassert(0 == rc); - myassert(0 != stmt.getNativePointer()); + affirm(0 == rc); + affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); - myassert(SQLITE_DONE == rc); + affirm(SQLITE_DONE == rc); sqlite3_finalize(stmt); - myassert(0 == stmt.getNativePointer()); + affirm(0 == stmt.getNativePointer()); { /* Demonstrate how to use the "zTail" option of sqlite3_prepare() family of functions. */ @@ -146,33 +144,33 @@ public class Tester1 { //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); + affirm(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 */ ); + case 1: affirm(19 == pos); break; + case 2: affirm(36 == pos); break; + default: affirm( false /* can't happen */ ); } ++n; - myassert(0 != stmt.getNativePointer()); + affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); - myassert(SQLITE_DONE == rc); + affirm(SQLITE_DONE == rc); sqlite3_finalize(stmt); - myassert(0 == stmt.getNativePointer()); + affirm(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()); + affirm(0 == rc); + affirm(0 != stmt.getNativePointer()); sqlite3_finalize(stmt); - myassert(0 == stmt.getNativePointer() ); + affirm(0 == stmt.getNativePointer() ); sqlite3_close_v2(db); } @@ -182,10 +180,10 @@ public class Tester1 { 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)); + affirm(0 == rc); + affirm(1 == sqlite3_bind_parameter_count(stmt)); final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a"); - myassert(1 == paramNdx); + affirm(1 == paramNdx); int total1 = 0; long rowid = -1; int changes = sqlite3_changes(db); @@ -195,34 +193,34 @@ public class Tester1 { for(int i = 99; i < 102; ++i ){ total1 += i; rc = sqlite3_bind_int(stmt, paramNdx, i); - myassert(0 == rc); + affirm(0 == rc); rc = sqlite3_step(stmt); sqlite3_reset(stmt); - myassert(SQLITE_DONE == rc); + affirm(SQLITE_DONE == rc); long x = sqlite3_last_insert_rowid(db); - myassert(x > rowid); + affirm(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); + affirm(total1 > 0); + affirm(sqlite3_changes(db) > changes); + affirm(sqlite3_total_changes(db) > changesT); + affirm(sqlite3_changes64(db) > changes64); + affirm(sqlite3_total_changes64(db) > changesT64); rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - myassert(0 == rc); + affirm(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) ); + affirm( null != sv ); + affirm( 0 != sv.getNativePointer() ); + affirm( SQLITE_INTEGER == sqlite3_value_type(sv) ); } sqlite3_finalize(stmt); - myassert(total1 == total2); + affirm(total1 == total2); sqlite3_close_v2(db); - myassert(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); } private static void testBindFetchInt64(){ @@ -239,13 +237,13 @@ public class Tester1 { } sqlite3_finalize(stmt); rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - myassert(0 == rc); + affirm(0 == rc); long total2 = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ total2 += sqlite3_column_int64(stmt, 0); } sqlite3_finalize(stmt); - myassert(total1 == total2); + affirm(total1 == total2); sqlite3_close_v2(db); } @@ -263,16 +261,16 @@ public class Tester1 { } sqlite3_finalize(stmt); rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - myassert(0 == rc); + affirm(0 == rc); double total2 = 0; int counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ ++counter; total2 += sqlite3_column_double(stmt, 0); } - myassert(4 == counter); + affirm(4 == counter); sqlite3_finalize(stmt); - myassert(total2<=total1+0.01 && total2>=total1-0.01); + affirm(total2<=total1+0.01 && total2>=total1-0.01); sqlite3_close_v2(db); } @@ -284,14 +282,14 @@ public class Tester1 { String list1[] = { "hell🤩", "w😃rld", "!" }; for( String e : list1 ){ rc = sqlite3_bind_text(stmt, 1, e); - myassert(0 == rc); + affirm(0 == rc); rc = sqlite3_step(stmt); - myassert(SQLITE_DONE==rc); + affirm(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); + affirm(0 == rc); StringBuffer sbuf = new StringBuffer(); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ @@ -301,8 +299,8 @@ public class Tester1 { ++n; } sqlite3_finalize(stmt); - myassert(3 == n); - myassert("w😃rldhell🤩!".equals(sbuf.toString())); + affirm(3 == n); + affirm("w😃rldhell🤩!".equals(sbuf.toString())); sqlite3_close_v2(db); } @@ -314,25 +312,25 @@ public class Tester1 { byte list1[] = { 0x32, 0x33, 0x34 }; rc = sqlite3_bind_blob(stmt, 1, list1); rc = sqlite3_step(stmt); - myassert(SQLITE_DONE == rc); + affirm(SQLITE_DONE == rc); sqlite3_finalize(stmt); rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - myassert(0 == rc); + affirm(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); + affirm(3 == blob.length); int i = 0; for(byte b : blob){ - myassert(b == list1[i++]); + affirm(b == list1[i++]); total += b; } ++n; } sqlite3_finalize(stmt); - myassert(1 == n); - myassert(total == 0x32 + 0x33 + 0x34); + affirm(1 == n); + affirm(total == 0x32 + 0x33 + 0x34); sqlite3_close_v2(db); } @@ -365,7 +363,7 @@ public class Tester1 { } }; int rc = sqlite3_create_collation(db, "reversi", SQLITE_UTF8, myCollation); - myassert(0 == rc); + affirm(0 == rc); sqlite3_stmt stmt = new sqlite3_stmt(); sqlite3_prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi", stmt); int counter = 0; @@ -374,12 +372,12 @@ public class Tester1 { ++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; + case 1: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 3: affirm("a".equals(val)); break; } } - myassert(3 == counter); + affirm(3 == counter); sqlite3_finalize(stmt); sqlite3_prepare(db, "SELECT a FROM t ORDER BY a", stmt); counter = 0; @@ -388,16 +386,16 @@ public class Tester1 { ++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; + case 3: affirm("c".equals(val)); break; + case 2: affirm("b".equals(val)); break; + case 1: affirm("a".equals(val)); break; } } - myassert(3 == counter); + affirm(3 == counter); sqlite3_finalize(stmt); - myassert(!xDestroyCalled.value); + affirm(!xDestroyCalled.value); sqlite3_close(db); - myassert(xDestroyCalled.value); + affirm(xDestroyCalled.value); } private static void testToUtf8(){ @@ -409,7 +407,7 @@ public class Tester1 { */ 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 */); + affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); //for( byte b : ba ) out( ""+b ); //outln(""); } @@ -430,7 +428,7 @@ public class Tester1 { // Java... new SQLFunction.Scalar(){ public void xFunc(sqlite3_context cx, sqlite3_value args[]){ - myassert(db.getNativePointer() + affirm(db.getNativePointer() == sqlite3_context_db_handle(cx).getNativePointer()); int result = 0; for( sqlite3_value v : args ) result += sqlite3_value_int(v); @@ -445,13 +443,13 @@ public class Tester1 { // Register and use the function... int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); - assert(0 == rc); - assert(0 == xFuncAccum.value); + affirm(0 == rc); + affirm(0 == xFuncAccum.value); execSql(db, "SELECT myfunc(1,2,3)"); - assert(6 == xFuncAccum.value); - assert( !xDestroyCalled.value ); + affirm(6 == xFuncAccum.value); + affirm( !xDestroyCalled.value ); sqlite3_close(db); - assert( xDestroyCalled.value ); + affirm( xDestroyCalled.value ); } private static void testUdfJavaObject(){ @@ -463,22 +461,22 @@ public class Tester1 { } }; int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); - assert(0 == rc); + affirm(0 == rc); sqlite3_stmt stmt = new sqlite3_stmt(); sqlite3_prepare(db, "select myfunc()", stmt); - assert( 0 != stmt.getNativePointer() ); + affirm( 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 == + affirm( testResult.value == sqlite3_value_java_object(v) ); + affirm( testResult.value == sqlite3_value_java_casted(v, Long.class) ); + affirm( testResult.value == sqlite3_value_java_casted(v, testResult.value.getClass()) ); - assert( null == sqlite3_value_java_casted(v, Double.class) ); + affirm( null == sqlite3_value_java_casted(v, Double.class) ); ++n; } sqlite3_finalize(stmt); - assert( 1 == n ); + affirm( 1 == n ); sqlite3_close(db); } @@ -496,14 +494,14 @@ public class Tester1 { }; 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); + affirm(0 == rc); sqlite3_stmt stmt = new sqlite3_stmt(); sqlite3_prepare(db, "select myfunc(a) from t", stmt); - assert( 0 != stmt.getNativePointer() ); + affirm( 0 != stmt.getNativePointer() ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ final int v = sqlite3_column_int(stmt, 0); - myassert( 6 == v ); + affirm( 6 == v ); ++n; } sqlite3_reset(stmt); @@ -511,11 +509,11 @@ public class Tester1 { n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ final int v = sqlite3_column_int(stmt, 0); - myassert( 6 == v ); + affirm( 6 == v ); ++n; } sqlite3_finalize(stmt); - assert( 1==n ); + affirm( 1==n ); sqlite3_close(db); } @@ -546,7 +544,7 @@ public class Tester1 { } }; int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func); - myassert( 0 == rc ); + affirm( 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)" @@ -557,22 +555,22 @@ public class Tester1 { "ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING"+ ") AS sum_y "+ "FROM twin ORDER BY x;", stmt); - myassert( 0 == rc ); + affirm( 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 */ ); + case 1: affirm( "a".equals(s) && 9==i ); break; + case 2: affirm( "b".equals(s) && 12==i ); break; + case 3: affirm( "c".equals(s) && 16==i ); break; + case 4: affirm( "d".equals(s) && 12==i ); break; + case 5: affirm( "e".equals(s) && 9==i ); break; + default: affirm( false /* cannot happen */ ); } } sqlite3_finalize(stmt); - myassert( 5 == n ); + affirm( 5 == n ); sqlite3_close(db); } @@ -621,23 +619,23 @@ public class Tester1 { switch(traceFlag){ case SQLITE_TRACE_STMT: // pNative ==> sqlite3_stmt - myassert(x instanceof String); break; + affirm(x instanceof String); break; case SQLITE_TRACE_PROFILE: // pNative ==> sqlite3_stmt - myassert(x instanceof Long); break; + affirm(x instanceof Long); break; case SQLITE_TRACE_ROW: // pNative ==> sqlite3_stmt case SQLITE_TRACE_CLOSE: // pNative ==> sqlite3 - myassert(null == x); + affirm(null == x); } return 0; } }); execSql(db, "SELECT 1; SELECT 2"); - myassert( 6 == counter.value ); + affirm( 6 == counter.value ); sqlite3_close(db); - myassert( 7 == counter.value ); + affirm( 7 == counter.value ); } private static void testBusy(){ @@ -647,10 +645,10 @@ public class Tester1 { final sqlite3 db2 = new sqlite3(); int rc = sqlite3_open(dbName, db1); - myassert( 0 == rc ); + affirm( 0 == rc ); execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); rc = sqlite3_open(dbName, db2); - myassert( 0 == rc ); + affirm( 0 == rc ); final ValueHolder xDestroyed = new ValueHolder<>(false); final ValueHolder xBusyCalled = new ValueHolder<>(0); @@ -664,20 +662,20 @@ public class Tester1 { } }; rc = sqlite3_busy_handler(db2, handler); - myassert(0 == rc); + affirm(0 == rc); // Force a locked condition... execSql(db1, "BEGIN EXCLUSIVE"); - myassert( false == xDestroyed.value ); + affirm( false == xDestroyed.value ); sqlite3_stmt stmt = new sqlite3_stmt(); rc = sqlite3_prepare(db2, "SELECT * from t", stmt); - myassert( SQLITE_BUSY == rc); - myassert( 3 == xBusyCalled.value ); + affirm( SQLITE_BUSY == rc); + affirm( 3 == xBusyCalled.value ); sqlite3_finalize(stmt); sqlite3_close(db1); - myassert( false == xDestroyed.value ); + affirm( false == xDestroyed.value ); sqlite3_close(db2); - myassert( true == xDestroyed.value ); + affirm( true == xDestroyed.value ); try{ final java.io.File f = new java.io.File(dbName); f.delete(); @@ -686,9 +684,9 @@ public class Tester1 { } } - private static void testMisc(){ - outln("Sleeping..."); - sqlite3_sleep(500); + private static void testSleep(){ + out("Sleeping briefly... "); + sqlite3_sleep(600); outln("Woke up."); } @@ -713,10 +711,10 @@ public class Tester1 { testUdfWindow(); testTrace(); testBusy(); - testMisc(); + testSleep(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); } - outln("Tests done. "+assertCount+" assertion(s) checked."); + outln("Tests done. "+affirmCount+" assertion checked."); } } diff --git a/manifest b/manifest index 45a4ee9515..67eaf44084 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\slocking\stest\sfor\sBusyHandler\sand\sclear\sup\snew\s-Xlint:jni\swarnings\sintroduced\sby\sthe\sbusy-handler\sbinding. -D 2023-07-27T22:42:25.017 +C Reformulate\sjni\stests\sto\snot\srequire\sthe\s-ea\sjvm\sflag\sto\senable\sassert(). +D 2023-07-27T22:53:02.373 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -241,7 +241,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 08a752b58a33696c5eaf0eb9361a0966 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 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 -F ext/jni/src/org/sqlite/jni/Tester1.java 7a5cc47aabca372a3bc9d078bdd90e10d00d517c3ebff19d023fd74272725ed9 +F ext/jni/src/org/sqlite/jni/Tester1.java 460d4a521bf3386a6aafc30c382817560b8dc1001472f6b8459cadeedb9a58ea 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 524747796a30a5c1c6c7567b49ffb1e35e2626c73e09c335c0ab74d4ddb5f005 -R 3c532f44a6a487cc227202d8226f03e9 +P 7dcde2bfce54b18f391776fa1cb93c0ff6153634bedcab0007b374c06c4d4079 +R ba5fe9ad4149aefc21881fee22f6fa73 U stephan -Z d6942649a24407ffbbac534429e75b47 +Z 2639e54196b7b2c8fbce104e63109714 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ab1bac8415..dea2791cda 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7dcde2bfce54b18f391776fa1cb93c0ff6153634bedcab0007b374c06c4d4079 \ No newline at end of file +dc356667a8f4fa31a3fef1ae35873d834d27fd6a9f0818d6fb85e4751fde9fe5 \ No newline at end of file From 48a8352a3932c091a5337aecc66626d3e6ec0386 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 01:12:47 +0000 Subject: [PATCH 006/148] Add support making use of sqlite3_aggregate_context() (in a roundabout way) from Java to accumulate state within aggregate and window UDFs. FossilOrigin-Name: 640574984741c7a9472d7f8be7bce87e736d7947ce673ae4a25008d74238ad90 --- ext/jni/src/c/sqlite3-jni.c | 64 +++++++++++++-- ext/jni/src/org/sqlite/jni/SQLFunction.java | 81 +++++++++++++++++-- ext/jni/src/org/sqlite/jni/Tester1.java | 57 ++++++++----- .../src/org/sqlite/jni/sqlite3_context.java | 34 ++++++++ manifest | 18 ++--- manifest.uuid | 2 +- 6 files changed, 214 insertions(+), 42 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 374518ac34..907c6a9d23 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -271,10 +271,11 @@ enum { typedef struct NphCacheLine NphCacheLine; struct NphCacheLine { const char * zClassName /* "full/class/Name" */; - jclass klazz /* global ref to concrete NPH class */; - jmethodID midSet /* setNativePointer() */; - jmethodID midGet /* getNativePointer() */; - jmethodID midCtor /* constructor */; + jclass klazz /* global ref to concrete NPH class */; + jmethodID midSet /* setNativePointer() */; + jmethodID midGet /* getNativePointer() */; + jmethodID midCtor /* constructor */; + jmethodID midSetAgg /* sqlite3_context::setAggregateContext() */; }; typedef struct JNIEnvCacheLine JNIEnvCacheLine; @@ -713,6 +714,42 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam } } +/** + Requires that jCx be a Java-side sqlite3_context wrapper for pCx. + This function calls sqlite3_aggregate_context() to allocate a tiny + sliver of memory, the address of which is set in + jCx->setAggregateContext(). The memory is only used as a key for + mapping, client-side, results of aggregate result sets across + xStep() and xFinal() methods. + + isFinal must be 1 for xFinal() calls and 0 for all others. +*/ +static void setAggregateContext(JNIEnv * env, jobject jCx, + sqlite3_context * pCx, + int isFinal){ + jmethodID setter; + void * pAgg; + struct NphCacheLine * const cacheLine = + S3Global_nph_cache(env, ClassNames.sqlite3_context); + if(cacheLine && cacheLine->klazz && cacheLine->midSetAgg){ + setter = cacheLine->midSetAgg; + assert(setter); + }else{ + jclass const klazz = + cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, jCx); + setter = (*env)->GetMethodID(env, klazz, "setAggregateContext", "(J)V"); + if(cacheLine){ + assert(cacheLine->klazz); + assert(!cacheLine->midSetAgg); + cacheLine->midSetAgg = setter; + } + } + pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 8); + (*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg); + IFTHREW_REPORT; +} + + /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own Java/JNI bindings. @@ -1054,6 +1091,11 @@ typedef struct { jobjectArray jargv; } udf_jargs; +/** + Converts the given (cx, argc, argv) into arguments for the given + UDF, placing the result in the final argument. Returns 0 on + success, SQLITE_NOMEM on allocation error. +*/ static int udf_args(sqlite3_context * const cx, int argc, sqlite3_value**argv, UDFState * const s, @@ -1102,19 +1144,23 @@ static int udf_report_exception(sqlite3_context * cx, UDFState *s, return rc; } -static int udf_xFSI(sqlite3_context* cx, int argc, +static int udf_xFSI(sqlite3_context* pCx, int argc, sqlite3_value** argv, UDFState * s, jmethodID xMethodID, const char * zFuncType){ udf_jargs args; JNIEnv * const env = s->env; - int rc = udf_args(cx, argc, argv, s, &args); + int rc = udf_args(pCx, argc, argv, s, &args); + //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); if(rc) return rc; //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + if( UDF_SCALAR != s->type ){ + setAggregateContext(env, args.jcx, pCx, 0); + } (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); IFTHREW{ - rc = udf_report_exception(cx,s, zFuncType); + rc = udf_report_exception(pCx,s, zFuncType); } UNREF_L(args.jcx); UNREF_L(args.jargv); @@ -1127,11 +1173,15 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s, JNIEnv * const env = s->env; jobject jcx = new_sqlite3_context_wrapper(s->env, cx); int rc = 0; + //MARKER(("%s.%s() cx = %p\n", s->zFuncName, zFuncType, cx)); if(!jcx){ sqlite3_result_error_nomem(cx); return SQLITE_NOMEM; } //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); + if( UDF_SCALAR != s->type ){ + setAggregateContext(env, jcx, cx, 1); + } (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); IFTHREW{ rc = udf_report_exception(cx,s, zFuncType); diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 482bf45338..7e7d817504 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -19,9 +19,62 @@ package org.sqlite.jni; 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. + + Note that if a given function is called multiple times in a single + SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., then the + context object passed to each one will be different. This is most + significant for aggregates and window functions, since they must + assign their results to the proper context. + + TODO: add helper APIs to map sqlite3_context instances to + func-specific state and to clear that when the aggregate or window + function is done. */ public abstract class SQLFunction { + /** + ContextMap is a helper for use with aggregate and window + functions, to help them manage their accumulator state across + calls to xStep() and xFinal(). It works by mapping + sqlite3_context::getAggregateContext() to a single piece of state + which persists across a set of 0 or more SQLFunction.xStep() + calls and 1 SQLFunction.xFinal() call. + */ + public static final class ContextMap { + private java.util.Map> map + = new java.util.HashMap>(); + + /** + Should be called from a UDF's xStep() method, passing it that + method's first argument and an initial value for the persistent + state. If there is currently no mapping for + cx.getAggregateContext() within the map, one is created, else + an existing one is preferred. It returns a ValueHolder which + can be used to modify that state directly without having to put + a new result back in the underlying map. + */ + public ValueHolder xStep(sqlite3_context cx, T initialValue){ + ValueHolder rc = map.get(cx.getAggregateContext()); + if(null == rc){ + map.put(cx.getAggregateContext(), rc = new ValueHolder(initialValue)); + } + return rc; + } + + /** + Should be called from a UDF's xFinal() method and passed that + method's first argument. This function returns the value + associated with cx.getAggregateContext(), or null if + this.xStep() has not been called to set up such a mapping. That + will be the case if an aggregate is used in a statement which + has no result rows. + */ + public T xFinal(sqlite3_context cx){ + final ValueHolder h = map.remove(cx.getAggregateContext()); + return null==h ? null : h.value; + } + } + //! Subclass for creating scalar functions. public static abstract class Scalar extends SQLFunction { public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); @@ -33,18 +86,36 @@ public abstract class SQLFunction { } //! Subclass for creating aggregate functions. - public static abstract class Aggregate extends SQLFunction { + 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() {} + + private final ContextMap map = new ContextMap<>(); + + /** + See ContextMap.xStep(). + */ + public final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + return map.xStep(cx, initialValue); + } + + /** + See ContextMap.xFinal(). + */ + public final T takeAggregateState(sqlite3_context cx){ + return map.xFinal(cx); + } } //! Subclass for creating window functions. - public static abstract class Window extends SQLFunction { - public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); + public static abstract class Window extends Aggregate { + public Window(){ + super(); + } + //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 xFinal(sqlite3_context cx); public abstract void xValue(sqlite3_context cx); - public void xDestroy() {} } } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 4b760661db..edf0eeaafa 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -482,21 +482,23 @@ public class Tester1 { 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]); + SQLFunction func = new SQLFunction.Aggregate(){ + @Override + public void xStep(sqlite3_context cx, sqlite3_value args[]){ + this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]); } - @Override public void xFinal(sqlite3_context cx){ - sqlite3_result_int(cx, this.accum); - this.accum = 0; + @Override + public void xFinal(sqlite3_context cx){ + final Integer v = this.takeAggregateState(cx); + if(null == v) sqlite3_result_null(cx); + else sqlite3_result_int(cx, v); } }; 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); affirm(0 == rc); sqlite3_stmt stmt = new sqlite3_stmt(); - sqlite3_prepare(db, "select myfunc(a) from t", stmt); + sqlite3_prepare(db, "select myfunc(a), myfunc(a+10) from t", stmt); affirm( 0 != stmt.getNativePointer() ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ @@ -514,6 +516,20 @@ public class Tester1 { } sqlite3_finalize(stmt); affirm( 1==n ); + + rc = sqlite3_prepare(db, "select myfunc(a), myfunc(a+a) from t order by a", + stmt); + affirm( 0 == rc ); + n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + final int c0 = sqlite3_column_int(stmt, 0); + final int c1 = sqlite3_column_int(stmt, 1); + ++n; + affirm( 6 == c0 ); + affirm( 12 == c1 ); + } + affirm( 1 == n ); + sqlite3_finalize(stmt); sqlite3_close(db); } @@ -521,26 +537,27 @@ public class Tester1 { 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); + final SQLFunction func = new SQLFunction.Window(){ + + private void xStepInverse(sqlite3_context cx, int v){ + this.getAggregateState(cx,0).value += v; } @Override public void xStep(sqlite3_context cx, sqlite3_value[] args){ - this.xStepInverse(sqlite3_value_int(args[0])); + this.xStepInverse(cx, sqlite3_value_int(args[0])); } @Override public void xInverse(sqlite3_context cx, sqlite3_value[] args){ - this.xStepInverse(-sqlite3_value_int(args[0])); + this.xStepInverse(cx, -sqlite3_value_int(args[0])); + } + + private void xFinalValue(sqlite3_context cx, Integer v){ + if(null == v) sqlite3_result_null(cx); + else sqlite3_result_int(cx, v); } @Override public void xFinal(sqlite3_context cx){ - this.xFinalValue(cx); - this.accum = 0; + xFinalValue(cx, this.takeAggregateState(cx)); } @Override public void xValue(sqlite3_context cx){ - this.xFinalValue(cx); + xFinalValue(cx, this.getAggregateState(cx,null).value); } }; int rc = sqlite3_create_function(db, "winsumint", 1, SQLITE_UTF8, func); diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java index 9d053ffe9a..d6bc3012a1 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -13,8 +13,42 @@ */ package org.sqlite.jni; +/** + sqlite3_context instances are used in conjunction with user-defined + SQL functions (a.k.a. UDFs). They are opaque pointers. + + The getAggregateContext() method corresponds to C's + sqlite3_aggregate_context(), with a slightly different interface in + order to account for cross-language differences. It serves the same + purposes in a slightly different way: it provides a key which is + stable across invocations of UDF xStep() and xFinal() pairs, to + which a UDF may map state across such calls (e.g. a numeric result + which is being accumulated). +*/ public class sqlite3_context extends NativePointerHolder { public sqlite3_context() { super(); } + private long aggcx = 0; + + /** + If this object is being used in the context of an aggregate or + window UDF, the UDF binding layer will set a unique context value + here. That value will be the same across matching calls to the + xStep() and xFinal() routines, as well as xValue() and xInverse() + in window UDFs. This value can be used as a key to map state + which needs to persist across such calls, noting that such state + should be cleaned up via xFinal(). + */ + public long getAggregateContext(){ + return aggcx; + } + + /** + For use only by the JNI layer. It's permitted to call this even + though it's private. + */ + private void setAggregateContext(long n){ + aggcx = n; + } } diff --git a/manifest b/manifest index 67eaf44084..7fe2d447fd 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Reformulate\sjni\stests\sto\snot\srequire\sthe\s-ea\sjvm\sflag\sto\senable\sassert(). -D 2023-07-27T22:53:02.373 +C Add\ssupport\smaking\suse\sof\ssqlite3_aggregate_context()\s(in\sa\sroundabout\sway)\sfrom\sJava\sto\saccumulate\sstate\swithin\saggregate\sand\swindow\sUDFs. +D 2023-07-28T01:12:47.322 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,20 +232,20 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 -F ext/jni/src/c/sqlite3-jni.c 76921edc2d1abea2cb39c21bcc49acbc307cb368e96cb7803a2c134c444c3fcd +F ext/jni/src/c/sqlite3-jni.c 8d3ae5c0474548b1b95fea888227a4f617b9302a7e230bb5ff1b3735fe85fb03 F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c 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/SQLFunction.java d77e0a4bb6bc0d65339aeacd6b20fc7e3b8a05f899c1f0ead90dda61f0a01522 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 -F ext/jni/src/org/sqlite/jni/Tester1.java 460d4a521bf3386a6aafc30c382817560b8dc1001472f6b8459cadeedb9a58ea +F ext/jni/src/org/sqlite/jni/Tester1.java 2334d1dd0efc22179654c586065c77d904830d736059b4049f9cd9e6832565bd 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_context.java 4a0b22226705a4f89d9c8093e0f51a8991cc0464864120970c915695afbba4e2 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7dcde2bfce54b18f391776fa1cb93c0ff6153634bedcab0007b374c06c4d4079 -R ba5fe9ad4149aefc21881fee22f6fa73 +P dc356667a8f4fa31a3fef1ae35873d834d27fd6a9f0818d6fb85e4751fde9fe5 +R 6f355aed3877133b3fb6aa6671123d94 U stephan -Z 2639e54196b7b2c8fbce104e63109714 +Z 75e450fbcee41582218a9562a53136a5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index dea2791cda..c7d6c30d99 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dc356667a8f4fa31a3fef1ae35873d834d27fd6a9f0818d6fb85e4751fde9fe5 \ No newline at end of file +640574984741c7a9472d7f8be7bce87e736d7947ce673ae4a25008d74238ad90 \ No newline at end of file From 7d207bf4836753fb0b73aa2773b7958807d4e5ec Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 01:19:44 +0000 Subject: [PATCH 007/148] Add an OOM check to the previous check-in. Minor internal API renaming. FossilOrigin-Name: 6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce --- ext/jni/src/c/sqlite3-jni.c | 31 ++++++++++++++++++++----------- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 907c6a9d23..fba90108d1 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -723,8 +723,10 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam xStep() and xFinal() methods. isFinal must be 1 for xFinal() calls and 0 for all others. + + Returns 0 on succes, SQLITE_NOMEM on allocation error. */ -static void setAggregateContext(JNIEnv * env, jobject jCx, +static int s3jni_setAggregateContext(JNIEnv * env, jobject jCx, sqlite3_context * pCx, int isFinal){ jmethodID setter; @@ -745,8 +747,11 @@ static void setAggregateContext(JNIEnv * env, jobject jCx, } } pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 8); - (*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg); - IFTHREW_REPORT; + if( pAgg ){ + (*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg); + IFTHREW_REPORT; + } + return pAgg ? (int)0 : SQLITE_NOMEM; } @@ -1156,11 +1161,13 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, if(rc) return rc; //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); if( UDF_SCALAR != s->type ){ - setAggregateContext(env, args.jcx, pCx, 0); + rc = s3jni_setAggregateContext(env, args.jcx, pCx, 0); } - (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); - IFTHREW{ - rc = udf_report_exception(pCx,s, zFuncType); + if( 0 == rc ){ + (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); + IFTHREW{ + rc = udf_report_exception(pCx,s, zFuncType); + } } UNREF_L(args.jcx); UNREF_L(args.jargv); @@ -1180,11 +1187,13 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s, } //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); if( UDF_SCALAR != s->type ){ - setAggregateContext(env, jcx, cx, 1); + rc = s3jni_setAggregateContext(env, jcx, cx, 1); } - (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); - IFTHREW{ - rc = udf_report_exception(cx,s, zFuncType); + if( 0 == rc ){ + (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); + IFTHREW{ + rc = udf_report_exception(cx,s, zFuncType); + } } UNREF_L(jcx); return rc; diff --git a/manifest b/manifest index 7fe2d447fd..058fbe8d09 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssupport\smaking\suse\sof\ssqlite3_aggregate_context()\s(in\sa\sroundabout\sway)\sfrom\sJava\sto\saccumulate\sstate\swithin\saggregate\sand\swindow\sUDFs. -D 2023-07-28T01:12:47.322 +C Add\san\sOOM\scheck\sto\sthe\sprevious\scheck-in.\sMinor\sinternal\sAPI\srenaming. +D 2023-07-28T01:19:44.606 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 -F ext/jni/src/c/sqlite3-jni.c 8d3ae5c0474548b1b95fea888227a4f617b9302a7e230bb5ff1b3735fe85fb03 +F ext/jni/src/c/sqlite3-jni.c 7e76652684c38df7c48ac3300056601202b0c45d91c5f3671725e17c3c69ec6a F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P dc356667a8f4fa31a3fef1ae35873d834d27fd6a9f0818d6fb85e4751fde9fe5 -R 6f355aed3877133b3fb6aa6671123d94 +P 640574984741c7a9472d7f8be7bce87e736d7947ce673ae4a25008d74238ad90 +R ce71c2fd0629446f06e751d7b64d5f7d U stephan -Z 75e450fbcee41582218a9562a53136a5 +Z 3429cf1394e9a8e9214fcef9821d7359 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c7d6c30d99..b7049ce4d7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -640574984741c7a9472d7f8be7bce87e736d7947ce673ae4a25008d74238ad90 \ No newline at end of file +6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce \ No newline at end of file From 75d3b1b5a20403cae8715e5d2500725565ca3789 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 01:51:14 +0000 Subject: [PATCH 008/148] More docs and cleanups related to the aggregate UDF state. Correct the OOM check to behave properly if xFinal() is called without a matching xStep(), xValue(), or xInverse(). FossilOrigin-Name: ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c --- ext/jni/src/c/sqlite3-jni.c | 88 ++++++++------- ext/jni/src/org/sqlite/jni/SQLFunction.java | 101 +++++++++--------- .../src/org/sqlite/jni/sqlite3_context.java | 9 +- manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 115 insertions(+), 101 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index fba90108d1..6303d87553 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -404,6 +404,33 @@ static void s3jni_free(void * p){ if(p) sqlite3_free(p); } + +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own Java/JNI bindings. +** +** For purposes of certain hand-crafted JNI function bindings, we +** need a way of reporting errors which is consistent with the rest of +** the C API, as opposed to throwing JS exceptions. To that end, this +** internal-use-only function is a thin proxy around +** sqlite3ErrorWithMessage(). The intent is that it only be used from +** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not +** from client code. +** +** Returns err_code. +*/ +static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ + if( db!=0 ){ + if( 0!=zMsg ){ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + }else{ + sqlite3ErrorWithMsg(db, err_code, NULL); + } + } + return err_code; +} + /** Clears s's state, releasing any Java references. Before doing so, it calls s's xDestroy() method, ignoring the lack of that method or @@ -724,13 +751,17 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam isFinal must be 1 for xFinal() calls and 0 for all others. - Returns 0 on succes, SQLITE_NOMEM on allocation error. + Returns 0 on success. Returns SQLITE_NOMEM on allocation error, + noting that it will not allocate when isFinal is true. It returns + SQLITE_ERROR if there's a serious internal error in dealing with + the JNI state. */ -static int s3jni_setAggregateContext(JNIEnv * env, jobject jCx, - sqlite3_context * pCx, - int isFinal){ +static int udf_setAggregateContext(JNIEnv * env, jobject jCx, + sqlite3_context * pCx, + int isFinal){ jmethodID setter; void * pAgg; + int rc = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, ClassNames.sqlite3_context); if(cacheLine && cacheLine->klazz && cacheLine->midSetAgg){ @@ -746,43 +777,24 @@ static int s3jni_setAggregateContext(JNIEnv * env, jobject jCx, cacheLine->midSetAgg = setter; } } - pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 8); - if( pAgg ){ + pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); + if( pAgg || isFinal ){ (*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg); - IFTHREW_REPORT; - } - return pAgg ? (int)0 : SQLITE_NOMEM; -} - - -/* -** This function is NOT part of the sqlite3 public API. It is strictly -** for use by the sqlite project's own Java/JNI bindings. -** -** For purposes of certain hand-crafted JNI function bindings, we -** need a way of reporting errors which is consistent with the rest of -** the C API, as opposed to throwing JS exceptions. To that end, this -** internal-use-only function is a thin proxy around -** sqlite3ErrorWithMessage(). The intent is that it only be used from -** JNI bindings such as sqlite3_prepare_v2/v3(), and definitely not -** from client code. -** -** Returns err_code. -*/ -static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ - if( db!=0 ){ - if( 0!=zMsg ){ - const int nMsg = sqlite3Strlen30(zMsg); - sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); - }else{ - sqlite3ErrorWithMsg(db, err_code, NULL); + IFTHREW { + EXCEPTION_REPORT; + EXCEPTION_CLEAR/*arguable, but so is propagation*/; + rc = s3jni_db_error(sqlite3_context_db_handle(pCx), + SQLITE_ERROR, + "sqlite3_context::setAggregateContext() " + "unexpectedly threw."); } + }else{ + assert(!pAgg); + rc = SQLITE_NOMEM; } - return err_code; + return rc; } - - /* Sets a native int32 value in OutputPointer.Int32 object ppOut. */ static void setOutputInt32(JNIEnv * env, jobject ppOut, int v){ jmethodID setter = 0; @@ -1161,7 +1173,7 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, if(rc) return rc; //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); if( UDF_SCALAR != s->type ){ - rc = s3jni_setAggregateContext(env, args.jcx, pCx, 0); + rc = udf_setAggregateContext(env, args.jcx, pCx, 0); } if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); @@ -1187,7 +1199,7 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s, } //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); if( UDF_SCALAR != s->type ){ - rc = s3jni_setAggregateContext(env, jcx, cx, 1); + rc = udf_setAggregateContext(env, jcx, cx, 1); } if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 7e7d817504..eaa83df9a9 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -18,42 +18,41 @@ package org.sqlite.jni; 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. - - Note that if a given function is called multiple times in a single - SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., then the - context object passed to each one will be different. This is most - significant for aggregates and window functions, since they must - assign their results to the proper context. - - TODO: add helper APIs to map sqlite3_context instances to - func-specific state and to clear that when the aggregate or window - function is done. + inner classes Scalar, Aggregate, and Window. */ public abstract class SQLFunction { /** ContextMap is a helper for use with aggregate and window functions, to help them manage their accumulator state across - calls to xStep() and xFinal(). It works by mapping + calls to the UDF's callbacks. + + If a given aggregate or window function is called multiple times + in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., + then the clients need some way of knowing which call is which so + that they can map their state between their various UDF callbacks + and reset it (if needed) via xFinal(). This class takes care of + such mappings. + + This class works by mapping sqlite3_context::getAggregateContext() to a single piece of state - which persists across a set of 0 or more SQLFunction.xStep() - calls and 1 SQLFunction.xFinal() call. - */ + which persists across a "matching set" of the UDF's callbacks. + */ public static final class ContextMap { private java.util.Map> map - = new java.util.HashMap>(); + = new java.util.HashMap<>(); /** - Should be called from a UDF's xStep() method, passing it that - method's first argument and an initial value for the persistent - state. If there is currently no mapping for - cx.getAggregateContext() within the map, one is created, else - an existing one is preferred. It returns a ValueHolder which - can be used to modify that state directly without having to put - a new result back in the underlying map. + Should be called from a UDF's xStep(), xValue(), and xInverse() + methods, passing it that method's first argument and an initial + value for the persistent state. If there is currently no + mapping for cx.getAggregateContext() within the map, one is + created using the given initial value, else an existing one is + use and the 2nd argument is ignored. It returns a ValueHolder + which can be used to modify that state directly without + requiring that the user update the underlying map. */ - public ValueHolder xStep(sqlite3_context cx, T initialValue){ + public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ ValueHolder rc = map.get(cx.getAggregateContext()); if(null == rc){ map.put(cx.getAggregateContext(), rc = new ValueHolder(initialValue)); @@ -63,13 +62,13 @@ public abstract class SQLFunction { /** Should be called from a UDF's xFinal() method and passed that - method's first argument. This function returns the value - associated with cx.getAggregateContext(), or null if - this.xStep() has not been called to set up such a mapping. That - will be the case if an aggregate is used in a statement which - has no result rows. + method's first argument. This function removes the value + associated with cx.getAggregateContext() from the map and + returns it, returning null if no other UDF method has not been + called to set up such a mapping. That will be the case if an + aggregate is used in a statement which has no result rows. */ - public T xFinal(sqlite3_context cx){ + public T takeAggregateState(sqlite3_context cx){ final ValueHolder h = map.remove(cx.getAggregateContext()); return null==h ? null : h.value; } @@ -79,43 +78,47 @@ public abstract class SQLFunction { 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. + Optionally override to be notified when the UDF is finalized by + SQLite. */ public void xDestroy() {} } - //! Subclass for creating aggregate functions. + /** + SQLFunction Subclass for creating aggregate functions. Its T is + the data type of its "accumulator" state, an instance of which is + intended to be be managed using the getAggregateState() and + takeAggregateState() methods. + */ public static abstract class Aggregate extends SQLFunction { public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); public abstract void xFinal(sqlite3_context cx); + + //! See Scalar.xDestroy() public void xDestroy() {} private final ContextMap map = new ContextMap<>(); - /** - See ContextMap.xStep(). - */ - public final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ - return map.xStep(cx, initialValue); + //! See ContextMap.getAggregateState(). + protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ + return map.getAggregateState(cx, initialValue); } - /** - See ContextMap.xFinal(). - */ - public final T takeAggregateState(sqlite3_context cx){ - return map.xFinal(cx); + //! See ContextMap.takeAggregateState(). + protected final T takeAggregateState(sqlite3_context cx){ + return map.takeAggregateState(cx); } } - //! Subclass for creating window functions. + /** + An SQLFunction subclass for creating window functions. Note that + Window inherits from Aggregate and each instance is + required to implemenat the inherited abstract methods from that + class. See Aggregate for information on managing the call + state across matching calls of the UDF callbacks. + */ public static abstract class Window extends Aggregate { - public Window(){ - super(); - } - //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); } } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java index d6bc3012a1..bf2224dd5e 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -34,11 +34,10 @@ public class sqlite3_context extends NativePointerHolder { /** If this object is being used in the context of an aggregate or window UDF, the UDF binding layer will set a unique context value - here. That value will be the same across matching calls to the - xStep() and xFinal() routines, as well as xValue() and xInverse() - in window UDFs. This value can be used as a key to map state - which needs to persist across such calls, noting that such state - should be cleaned up via xFinal(). + here, else this will return 0. That value will be the same across + matching calls to the UDF callbacks. This value can be used as a + key to map state which needs to persist across such calls, noting + that such state should be cleaned up via xFinal(). */ public long getAggregateContext(){ return aggcx; diff --git a/manifest b/manifest index 058fbe8d09..6237512571 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\san\sOOM\scheck\sto\sthe\sprevious\scheck-in.\sMinor\sinternal\sAPI\srenaming. -D 2023-07-28T01:19:44.606 +C More\sdocs\sand\scleanups\srelated\sto\sthe\saggregate\sUDF\sstate.\sCorrect\sthe\sOOM\scheck\sto\sbehave\sproperly\sif\sxFinal()\sis\scalled\swithout\sa\smatching\sxStep(),\sxValue(),\sor\sxInverse(). +D 2023-07-28T01:51:14.668 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,20 +232,20 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 -F ext/jni/src/c/sqlite3-jni.c 7e76652684c38df7c48ac3300056601202b0c45d91c5f3671725e17c3c69ec6a +F ext/jni/src/c/sqlite3-jni.c 9464d7f186c52cecd4c6ac91d3da35f29fd98923a048befc8d2d872edd639a41 F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c 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 d77e0a4bb6bc0d65339aeacd6b20fc7e3b8a05f899c1f0ead90dda61f0a01522 +F ext/jni/src/org/sqlite/jni/SQLFunction.java b176c46828a52084dd3a39e5084d0b0ce12dcaf2abe719a58f4d1d92733e1136 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 F ext/jni/src/org/sqlite/jni/Tester1.java 2334d1dd0efc22179654c586065c77d904830d736059b4049f9cd9e6832565bd 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 4a0b22226705a4f89d9c8093e0f51a8991cc0464864120970c915695afbba4e2 +F ext/jni/src/org/sqlite/jni/sqlite3_context.java 4e7eebc8a5c85ecfbae3aa2c4ddb7f1ca861c218d3829d31afe16f6b11104213 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 640574984741c7a9472d7f8be7bce87e736d7947ce673ae4a25008d74238ad90 -R ce71c2fd0629446f06e751d7b64d5f7d +P 6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce +R 0fe909577d9a504bc5127b45fc11fe45 U stephan -Z 3429cf1394e9a8e9214fcef9821d7359 +Z 8f663d2013372069850b9c169f30fdc3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b7049ce4d7..28aa247cb8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce \ No newline at end of file +ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c \ No newline at end of file From 09c2640fe3153f1855268596479ba80f4c851d53 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 09:25:05 +0000 Subject: [PATCH 009/148] More Java docs about making use of the aggregate context. Change the JNI mapping to set the sqlite3_context::aggregateContext member directly, instead of via a superflous setter, because that way is faster. FossilOrigin-Name: 7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45 --- ext/jni/src/c/sqlite3-jni.c | 44 ++++++------ ext/jni/src/org/sqlite/jni/SQLFunction.java | 12 +++- ext/jni/src/org/sqlite/jni/Tester1.java | 1 - .../src/org/sqlite/jni/sqlite3_context.java | 67 ++++++++++++------- manifest | 18 ++--- manifest.uuid | 2 +- 6 files changed, 84 insertions(+), 60 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 6303d87553..42c46b755b 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -275,7 +275,7 @@ struct NphCacheLine { jmethodID midSet /* setNativePointer() */; jmethodID midGet /* getNativePointer() */; jmethodID midCtor /* constructor */; - jmethodID midSetAgg /* sqlite3_context::setAggregateContext() */; + jfieldID fidSetAgg /* sqlite3_context::aggregateContext */; }; typedef struct JNIEnvCacheLine JNIEnvCacheLine; @@ -745,11 +745,14 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam Requires that jCx be a Java-side sqlite3_context wrapper for pCx. This function calls sqlite3_aggregate_context() to allocate a tiny sliver of memory, the address of which is set in - jCx->setAggregateContext(). The memory is only used as a key for - mapping, client-side, results of aggregate result sets across - xStep() and xFinal() methods. + jCx->aggregateContext. The memory is only used as a key for + mapping client-side results of aggregate result sets across + calls to the UDF's callbacks. - isFinal must be 1 for xFinal() calls and 0 for all others. + isFinal must be 1 for xFinal() calls and 0 for all others, the + difference being that the xFinal() invocation will not allocate + new memory if it was not already, resulting in a value of 0 + for jCx->aggregateContext. Returns 0 on success. Returns SQLITE_NOMEM on allocation error, noting that it will not allocate when isFinal is true. It returns @@ -759,35 +762,34 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam static int udf_setAggregateContext(JNIEnv * env, jobject jCx, sqlite3_context * pCx, int isFinal){ - jmethodID setter; + jfieldID member; void * pAgg; int rc = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, ClassNames.sqlite3_context); - if(cacheLine && cacheLine->klazz && cacheLine->midSetAgg){ - setter = cacheLine->midSetAgg; - assert(setter); + if(cacheLine && cacheLine->klazz && cacheLine->fidSetAgg){ + member = cacheLine->fidSetAgg; + assert(member); }else{ jclass const klazz = cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, jCx); - setter = (*env)->GetMethodID(env, klazz, "setAggregateContext", "(J)V"); + member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); + if( !member ){ + IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } + return s3jni_db_error(sqlite3_context_db_handle(pCx), + SQLITE_ERROR, + "Internal error: cannot find " + "sqlite3_context::aggregateContext field."); + } if(cacheLine){ assert(cacheLine->klazz); - assert(!cacheLine->midSetAgg); - cacheLine->midSetAgg = setter; + assert(!cacheLine->fidSetAgg); + cacheLine->fidSetAgg = member; } } pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); if( pAgg || isFinal ){ - (*env)->CallVoidMethod(env, jCx, setter, (jlong)pAgg); - IFTHREW { - EXCEPTION_REPORT; - EXCEPTION_CLEAR/*arguable, but so is propagation*/; - rc = s3jni_db_error(sqlite3_context_db_handle(pCx), - SQLITE_ERROR, - "sqlite3_context::setAggregateContext() " - "unexpectedly threw."); - } + (*env)->SetLongField(env, jCx, member, (jlong)pAgg); }else{ assert(!pAgg); rc = SQLITE_NOMEM; diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index eaa83df9a9..396602b959 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -35,11 +35,17 @@ public abstract class SQLFunction { such mappings. This class works by mapping - sqlite3_context::getAggregateContext() to a single piece of state - which persists across a "matching set" of the UDF's callbacks. + sqlite3_context.getAggregateContext() to a single piece of + state, of a client-defined type (the T part of this class), which + persists across a "matching set" of the UDF's callbacks. + + This class is a helper providing commonly-needed functionality - + it is not required for use with aggregate or window functions. + Client UDFs are free to perform such mappings using custom + approaches. */ public static final class ContextMap { - private java.util.Map> map + private final java.util.Map> map = new java.util.HashMap<>(); /** diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index edf0eeaafa..77495dad84 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -656,7 +656,6 @@ public class Tester1 { } private static void testBusy(){ - outln("testBusy()..."); final String dbName = "_busy-handler.db"; final sqlite3 db1 = new sqlite3(); final sqlite3 db2 = new sqlite3(); diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java index bf2224dd5e..3272465299 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -15,39 +15,56 @@ package org.sqlite.jni; /** sqlite3_context instances are used in conjunction with user-defined - SQL functions (a.k.a. UDFs). They are opaque pointers. - - The getAggregateContext() method corresponds to C's - sqlite3_aggregate_context(), with a slightly different interface in - order to account for cross-language differences. It serves the same - purposes in a slightly different way: it provides a key which is - stable across invocations of UDF xStep() and xFinal() pairs, to - which a UDF may map state across such calls (e.g. a numeric result - which is being accumulated). + SQL functions (a.k.a. UDFs). */ public class sqlite3_context extends NativePointerHolder { public sqlite3_context() { super(); } - private long aggcx = 0; /** - If this object is being used in the context of an aggregate or - window UDF, the UDF binding layer will set a unique context value - here, else this will return 0. That value will be the same across - matching calls to the UDF callbacks. This value can be used as a - key to map state which needs to persist across such calls, noting - that such state should be cleaned up via xFinal(). - */ - public long getAggregateContext(){ - return aggcx; - } - - /** - For use only by the JNI layer. It's permitted to call this even + For use only by the JNI layer. It's permitted to set this even though it's private. */ - private void setAggregateContext(long n){ - aggcx = n; + private long aggregateContext = 0; + + /** + getAggregateContext() corresponds to C's + sqlite3_aggregate_context(), with a slightly different interface + to account for cross-language differences. It serves the same + purposes in a slightly different way: it provides a key which is + stable across invocations of "matching sets" of a UDF's callbacks, + such that all calls into those callbacks can determine which "set" + of those calls they belong to. + + If this object is being used in the context of an aggregate or + window UDF, this function returns a non-0 value which is distinct + for each set of UDF callbacks from a single invocation of the + UDF, otherwise it returns 0. The returned value is only only + valid within the context of execution of a single SQL statement, + and may be re-used by future invocations of the UDF in different + SQL statements. + + Consider this SQL, where MYFUNC is a user-defined aggregate function: + + SELECT MYFUNC(A), MYFUNC(B) FROM T; + + The xStep() and xFinal() methods of the callback need to be able + to differentiate between those two invocations in order to + perform their work properly. The value returned by + getAggregateContext() will be distinct for each of those + invocations of MYFUNC() and is intended to be used as a lookup + key for mapping callback invocations to whatever client-defined + state is needed by the UDF. + + There is one case where this will return 0 in the context of an + aggregate or window function: if the result set has no rows, + the UDF's xFinal() will be called without any other x...() members + having been called. In that one case, no aggregate context key will + have been generated. xFinal() implementations need to be prepared to + accept that condition as legal. + */ + public long getAggregateContext(){ + return aggregateContext; } } diff --git a/manifest b/manifest index 6237512571..324f25d9f9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sdocs\sand\scleanups\srelated\sto\sthe\saggregate\sUDF\sstate.\sCorrect\sthe\sOOM\scheck\sto\sbehave\sproperly\sif\sxFinal()\sis\scalled\swithout\sa\smatching\sxStep(),\sxValue(),\sor\sxInverse(). -D 2023-07-28T01:51:14.668 +C More\sJava\sdocs\sabout\smaking\suse\sof\sthe\saggregate\scontext.\sChange\sthe\sJNI\smapping\sto\sset\sthe\ssqlite3_context::aggregateContext\smember\sdirectly,\sinstead\sof\svia\sa\ssuperflous\ssetter,\sbecause\sthat\sway\sis\sfaster. +D 2023-07-28T09:25:05.029 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,20 +232,20 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 -F ext/jni/src/c/sqlite3-jni.c 9464d7f186c52cecd4c6ac91d3da35f29fd98923a048befc8d2d872edd639a41 +F ext/jni/src/c/sqlite3-jni.c 9d0d58f3633bd8f467f893f45548873ed2c5451c673b0782b3cc6bfa92327b10 F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c 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 b176c46828a52084dd3a39e5084d0b0ce12dcaf2abe719a58f4d1d92733e1136 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 268291ee7be1406b13a3b220df2eac59b9337473d5eb9fa40bd528eefb57252c F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 -F ext/jni/src/org/sqlite/jni/Tester1.java 2334d1dd0efc22179654c586065c77d904830d736059b4049f9cd9e6832565bd +F ext/jni/src/org/sqlite/jni/Tester1.java 7d8742eb6d6aba429171b2ba6136f4f17569a280676d846cbe319fa95a97ae4d 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 4e7eebc8a5c85ecfbae3aa2c4ddb7f1ca861c218d3829d31afe16f6b11104213 +F ext/jni/src/org/sqlite/jni/sqlite3_context.java 841ac0384ec23e7d24ad9a928f8728b98bd3c4c3814d401200c6531786b9c241 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 6b56e4d62b4945e52978d00aa8e2984faa731c92a7e002e81524fcfcf8ba0cce -R 0fe909577d9a504bc5127b45fc11fe45 +P ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c +R ce373f0682789ebfcc8ec45219320dda U stephan -Z 8f663d2013372069850b9c169f30fdc3 +Z 02ac0e260dba2cb9492d0ae9287f8a5f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 28aa247cb8..b0a799140d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c \ No newline at end of file +7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45 \ No newline at end of file From f326577619a3445cb856694232bb60f0dfbb2cd1 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 09:54:39 +0000 Subject: [PATCH 010/148] Tweaks to the Java-side UDF docs. FossilOrigin-Name: 48e0079d8db675a8761f27348bc6795aa6b6e49a3f76bd4adece83309dcf2aff --- ext/jni/src/org/sqlite/jni/SQLFunction.java | 30 +++++++++++---------- manifest | 12 ++++----- manifest.uuid | 2 +- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 396602b959..78b716dd22 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -53,15 +53,16 @@ public abstract class SQLFunction { methods, passing it that method's first argument and an initial value for the persistent state. If there is currently no mapping for cx.getAggregateContext() within the map, one is - created using the given initial value, else an existing one is - use and the 2nd argument is ignored. It returns a ValueHolder - which can be used to modify that state directly without - requiring that the user update the underlying map. + created using the given initial value, else the existing one is + used and the 2nd argument is ignored. It returns a + ValueHolder which can be used to modify that state directly + without requiring that the client update the underlying map's + entry. */ public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ ValueHolder rc = map.get(cx.getAggregateContext()); if(null == rc){ - map.put(cx.getAggregateContext(), rc = new ValueHolder(initialValue)); + map.put(cx.getAggregateContext(), rc = new ValueHolder<>(initialValue)); } return rc; } @@ -70,9 +71,10 @@ public abstract class SQLFunction { Should be called from a UDF's xFinal() method and passed that method's first argument. This function removes the value associated with cx.getAggregateContext() from the map and - returns it, returning null if no other UDF method has not been - called to set up such a mapping. That will be the case if an - aggregate is used in a statement which has no result rows. + returns it, returning null if no other UDF method has been + called to set up such a mapping. The latter condition will be + the case if an aggregate is used in a statement which has no + result rows. */ public T takeAggregateState(sqlite3_context cx){ final ValueHolder h = map.remove(cx.getAggregateContext()); @@ -100,17 +102,17 @@ public abstract class SQLFunction { public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); public abstract void xFinal(sqlite3_context cx); - //! See Scalar.xDestroy() + //! @see Scalar#xDestroy() public void xDestroy() {} private final ContextMap map = new ContextMap<>(); - //! See ContextMap.getAggregateState(). + //! @see ContextMap#getAggregateState() protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ return map.getAggregateState(cx, initialValue); } - //! See ContextMap.takeAggregateState(). + //! @see ContextMap#takeAggregateState() protected final T takeAggregateState(sqlite3_context cx){ return map.takeAggregateState(cx); } @@ -119,9 +121,9 @@ public abstract class SQLFunction { /** An SQLFunction subclass for creating window functions. Note that Window inherits from Aggregate and each instance is - required to implemenat the inherited abstract methods from that - class. See Aggregate for information on managing the call - state across matching calls of the UDF callbacks. + required to implement the inherited abstract methods from that + class. See Aggregate for information on managing the UDF's + invocation-specific state. */ public static abstract class Window extends Aggregate { public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); diff --git a/manifest b/manifest index 324f25d9f9..a9fd93424c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sJava\sdocs\sabout\smaking\suse\sof\sthe\saggregate\scontext.\sChange\sthe\sJNI\smapping\sto\sset\sthe\ssqlite3_context::aggregateContext\smember\sdirectly,\sinstead\sof\svia\sa\ssuperflous\ssetter,\sbecause\sthat\sway\sis\sfaster. -D 2023-07-28T09:25:05.029 +C Tweaks\sto\sthe\sJava-side\sUDF\sdocs. +D 2023-07-28T09:54:39.514 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -239,7 +239,7 @@ F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413 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 268291ee7be1406b13a3b220df2eac59b9337473d5eb9fa40bd528eefb57252c +F ext/jni/src/org/sqlite/jni/SQLFunction.java f35e0607e366f2efa9e220d3c7cbeac9470c74f7f612f18c2ba7b105d4bead59 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 F ext/jni/src/org/sqlite/jni/Tester1.java 7d8742eb6d6aba429171b2ba6136f4f17569a280676d846cbe319fa95a97ae4d F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ff53f1ccdc1780f2d9bd5f59804a76dbdf4f6b70696d3a7dbdbd96d1f8f6fa5c -R ce373f0682789ebfcc8ec45219320dda +P 7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45 +R fe9104758f9885099777084c98a80013 U stephan -Z 02ac0e260dba2cb9492d0ae9287f8a5f +Z de93089c27325a9ac487304a0c51ee31 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b0a799140d..a01414088c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45 \ No newline at end of file +48e0079d8db675a8761f27348bc6795aa6b6e49a3f76bd4adece83309dcf2aff \ No newline at end of file From 069029011e9fb4efc1cba51dcc52e69bb5d22b6e Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 10:01:01 +0000 Subject: [PATCH 011/148] Add Java tests which ensure that xFinal() is called with no aggregate context when the result set is empty. FossilOrigin-Name: 7ac8c66a6b62d6bb345e8b9957a26de463019ae7d30ff442f19482c3a6827fc7 --- ext/jni/src/org/sqlite/jni/SQLFunction.java | 3 +++ ext/jni/src/org/sqlite/jni/Tester1.java | 17 +++++++++++++++-- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 78b716dd22..289f3a0396 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -58,6 +58,9 @@ public abstract class SQLFunction { ValueHolder which can be used to modify that state directly without requiring that the client update the underlying map's entry. + + T must be of a type which can be legally stored as a value in + java.util.HashMap. */ public ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ ValueHolder rc = map.get(cx.getAggregateContext()); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 77495dad84..d277150851 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -482,6 +482,10 @@ public class Tester1 { private static void testUdfAggregate(){ final sqlite3 db = createNewDb(); + final ValueHolder xFinalNull = + // To confirm that xFinal() is called with no aggregate state + // when the corresponding result set is empty. + new ValueHolder<>(false); SQLFunction func = new SQLFunction.Aggregate(){ @Override public void xStep(sqlite3_context cx, sqlite3_value args[]){ @@ -490,8 +494,12 @@ public class Tester1 { @Override public void xFinal(sqlite3_context cx){ final Integer v = this.takeAggregateState(cx); - if(null == v) sqlite3_result_null(cx); - else sqlite3_result_int(cx, v); + if(null == v){ + xFinalNull.value = true; + sqlite3_result_null(cx); + }else{ + sqlite3_result_int(cx, v); + } } }; execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES(1),(2),(3)"); @@ -506,6 +514,7 @@ public class Tester1 { affirm( 6 == v ); ++n; } + affirm( false == xFinalNull.value ); sqlite3_reset(stmt); // Ensure that the accumulator is reset... n = 0; @@ -529,7 +538,11 @@ public class Tester1 { affirm( 12 == c1 ); } affirm( 1 == n ); + affirm( false == xFinalNull.value ); sqlite3_finalize(stmt); + + execSql(db, "SELECT myfunc(1) WHERE 0"); + affirm( true == xFinalNull.value ); sqlite3_close(db); } diff --git a/manifest b/manifest index a9fd93424c..f6bccf7dd4 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Tweaks\sto\sthe\sJava-side\sUDF\sdocs. -D 2023-07-28T09:54:39.514 +C Add\sJava\stests\swhich\sensure\sthat\sxFinal()\sis\scalled\swith\sno\saggregate\scontext\swhen\sthe\sresult\sset\sis\sempty. +D 2023-07-28T10:01:01.739 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -239,9 +239,9 @@ F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413 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 f35e0607e366f2efa9e220d3c7cbeac9470c74f7f612f18c2ba7b105d4bead59 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 -F ext/jni/src/org/sqlite/jni/Tester1.java 7d8742eb6d6aba429171b2ba6136f4f17569a280676d846cbe319fa95a97ae4d +F ext/jni/src/org/sqlite/jni/Tester1.java 5cc3e28a1a1ee06d3a55117593acc432d08a9aa399a3a06dba5b994e91ca7cff 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7af0cb998f7161296d5e5e50a42e9db26ec13c145c61194a999a1a0104818d45 -R fe9104758f9885099777084c98a80013 +P 48e0079d8db675a8761f27348bc6795aa6b6e49a3f76bd4adece83309dcf2aff +R eae0975d651446c72791fe2a501172ec U stephan -Z de93089c27325a9ac487304a0c51ee31 +Z 694fd4327df7c4229eec43f11fc0825b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a01414088c..71a43b8590 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -48e0079d8db675a8761f27348bc6795aa6b6e49a3f76bd4adece83309dcf2aff \ No newline at end of file +7ac8c66a6b62d6bb345e8b9957a26de463019ae7d30ff442f19482c3a6827fc7 \ No newline at end of file From c7f602497bf021bcd5c42bce41336557e3d64ef4 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 10:37:17 +0000 Subject: [PATCH 012/148] Minor java cleanups recommended by a modern IDE. FossilOrigin-Name: f92a9bbabebc33a771835e259b04a57d07add1eacf3d9aec5fddf929ce8a3022 --- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 4 ++-- ext/jni/src/org/sqlite/jni/Tester1.java | 26 +++++++++++----------- manifest | 14 ++++++------ manifest.uuid | 2 +- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index b0ae33c640..eabe1c8609 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -200,7 +200,7 @@ public final class SQLite3Jni { int ndx, @NotNull Class type){ final Object o = sqlite3_column_java_object(stmt, ndx); - return (null!=o && type.isInstance(o)) ? (T)o : null; + return type.isInstance(o) ? (T)o : null; } public static native String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx); @@ -700,7 +700,7 @@ public final class SQLite3Jni { public static T sqlite3_value_java_casted(@NotNull sqlite3_value v, @NotNull Class type){ final Object o = sqlite3_value_java_object(v); - return (null!=o && type.isInstance(o)) ? (T)o : null; + return type.isInstance(o) ? (T)o : null; } /** diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index d277150851..b2772cc48b 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -202,7 +202,7 @@ public class Tester1 { rowid = x; } sqlite3_finalize(stmt); - affirm(total1 > 0); + affirm(300 == total1); affirm(sqlite3_changes(db) > changes); affirm(sqlite3_total_changes(db) > changesT); affirm(sqlite3_changes64(db) > changes64); @@ -279,7 +279,7 @@ public class Tester1 { 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", "!" }; + String[] list1 = { "hell🤩", "w😃rld", "!" }; for( String e : list1 ){ rc = sqlite3_bind_text(stmt, 1, e); affirm(0 == rc); @@ -290,7 +290,7 @@ public class Tester1 { sqlite3_finalize(stmt); rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); affirm(0 == rc); - StringBuffer sbuf = new StringBuffer(); + StringBuilder sbuf = new StringBuilder(); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ String txt = sqlite3_column_text(stmt, 0); @@ -309,7 +309,7 @@ public class Tester1 { 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 }; + byte[] list1 = { 0x32, 0x33, 0x34 }; rc = sqlite3_bind_blob(stmt, 1, list1); rc = sqlite3_step(stmt); affirm(SQLITE_DONE == rc); @@ -319,7 +319,7 @@ public class Tester1 { int n = 0; int total = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ - byte blob[] = sqlite3_column_blob(stmt, 0); + byte[] blob = sqlite3_column_blob(stmt, 0); affirm(3 == blob.length); int i = 0; for(byte b : blob){ @@ -427,7 +427,7 @@ public class Tester1 { // result is possibly somewhat noisy for those not at home in // Java... new SQLFunction.Scalar(){ - public void xFunc(sqlite3_context cx, sqlite3_value args[]){ + public void xFunc(sqlite3_context cx, sqlite3_value[] args){ affirm(db.getNativePointer() == sqlite3_context_db_handle(cx).getNativePointer()); int result = 0; @@ -488,7 +488,7 @@ public class Tester1 { new ValueHolder<>(false); SQLFunction func = new SQLFunction.Aggregate(){ @Override - public void xStep(sqlite3_context cx, sqlite3_value args[]){ + public void xStep(sqlite3_context cx, sqlite3_value[] args){ this.getAggregateState(cx, 0).value += sqlite3_value_int(args[0]); } @Override @@ -514,7 +514,7 @@ public class Tester1 { affirm( 6 == v ); ++n; } - affirm( false == xFinalNull.value ); + affirm(!xFinalNull.value); sqlite3_reset(stmt); // Ensure that the accumulator is reset... n = 0; @@ -538,11 +538,11 @@ public class Tester1 { affirm( 12 == c1 ); } affirm( 1 == n ); - affirm( false == xFinalNull.value ); + affirm(!xFinalNull.value); sqlite3_finalize(stmt); execSql(db, "SELECT myfunc(1) WHERE 0"); - affirm( true == xFinalNull.value ); + affirm(xFinalNull.value); sqlite3_close(db); } @@ -695,16 +695,16 @@ public class Tester1 { // Force a locked condition... execSql(db1, "BEGIN EXCLUSIVE"); - affirm( false == xDestroyed.value ); + affirm(!xDestroyed.value); sqlite3_stmt stmt = new sqlite3_stmt(); rc = sqlite3_prepare(db2, "SELECT * from t", stmt); affirm( SQLITE_BUSY == rc); affirm( 3 == xBusyCalled.value ); sqlite3_finalize(stmt); sqlite3_close(db1); - affirm( false == xDestroyed.value ); + affirm(!xDestroyed.value); sqlite3_close(db2); - affirm( true == xDestroyed.value ); + affirm(xDestroyed.value); try{ final java.io.File f = new java.io.File(dbName); f.delete(); diff --git a/manifest b/manifest index f6bccf7dd4..39b38b4cd4 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sJava\stests\swhich\sensure\sthat\sxFinal()\sis\scalled\swith\sno\saggregate\scontext\swhen\sthe\sresult\sset\sis\sempty. -D 2023-07-28T10:01:01.739 +C Minor\sjava\scleanups\srecommended\sby\sa\smodern\sIDE. +D 2023-07-28T10:37:17.675 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -240,8 +240,8 @@ F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 70dc7bc41f80352ff3d4331e2e 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 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3582b30c0fb1cb39e25b9069fe8c9e2fe4f2659f4d38437b610e46143e163610 -F ext/jni/src/org/sqlite/jni/Tester1.java 5cc3e28a1a1ee06d3a55117593acc432d08a9aa399a3a06dba5b994e91ca7cff +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f7939d75d26481d02ea3418fa24bb6f6ba89b98a3e10ac4ab67b7186f9cb4fda +F ext/jni/src/org/sqlite/jni/Tester1.java 6c819172137bf06d76f26df20c4c846abc82523db62c51cc043ac66031298103 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 48e0079d8db675a8761f27348bc6795aa6b6e49a3f76bd4adece83309dcf2aff -R eae0975d651446c72791fe2a501172ec +P 7ac8c66a6b62d6bb345e8b9957a26de463019ae7d30ff442f19482c3a6827fc7 +R e8f387c4dfdd92056cc99f13354e3b0d U stephan -Z 694fd4327df7c4229eec43f11fc0825b +Z f4a979401c828d3aa03901d37feaa60f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 71a43b8590..193d5d1547 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7ac8c66a6b62d6bb345e8b9957a26de463019ae7d30ff442f19482c3a6827fc7 \ No newline at end of file +f92a9bbabebc33a771835e259b04a57d07add1eacf3d9aec5fddf929ce8a3022 \ No newline at end of file From f69084aaf7792725841b8fe2353902e4c5c567d4 Mon Sep 17 00:00:00 2001 From: drh <> Date: Fri, 28 Jul 2023 15:58:09 +0000 Subject: [PATCH 013/148] Fix minor typos in the JNI README.md file. FossilOrigin-Name: e21cf2e8f13465dbff33f9c21580752c1d8c077d6a253f56f04dab0d47eb99b8 --- ext/jni/README.md | 8 ++++---- manifest | 14 +++++++------- manifest.uuid | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index 4cb76705f3..e1a91507cd 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -1,7 +1,7 @@ SQLite3 via JNI ======================================================================== -This repository houses a Java Native Interface (JNI) binding for the +This directory houses a Java Native Interface (JNI) binding for the sqlite3 API. > **FOREWARNING:** this subproject is very much in development and @@ -85,7 +85,7 @@ 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 +taken when converting Java strings to UTF-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 @@ -94,7 +94,7 @@ conversion in Java, and there is no JNI C API for that conversion [modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 -Unweildy Constructs are Re-mapped +Unwieldy Constructs are Re-mapped ------------------------------------------------------------------------ Some constructs, when modelled 1-to-1 from C to Java, are unduly @@ -186,7 +186,7 @@ Noting that: The [`sqlite3_create_function()`](https://sqlite.org/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: +binding. The Java API has only one core function-registration function: ``` int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, diff --git a/manifest b/manifest index 39b38b4cd4..e481249598 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sjava\scleanups\srecommended\sby\sa\smodern\sIDE. -D 2023-07-28T10:37:17.675 +C Fix\sminor\stypos\sin\sthe\sJNI\sREADME.md\sfile. +D 2023-07-28T15:58:09.824 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d -F ext/jni/README.md 042762dbf047667783a5bd0aec303535140f302debfbd259c612edf856661623 +F ext/jni/README.md ad1f948f0681951e516c667a68f4c195019100b271470fa8728e7c439b53bb84 F ext/jni/src/c/sqlite3-jni.c 9d0d58f3633bd8f467f893f45548873ed2c5451c673b0782b3cc6bfa92327b10 F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7ac8c66a6b62d6bb345e8b9957a26de463019ae7d30ff442f19482c3a6827fc7 -R e8f387c4dfdd92056cc99f13354e3b0d -U stephan -Z f4a979401c828d3aa03901d37feaa60f +P f92a9bbabebc33a771835e259b04a57d07add1eacf3d9aec5fddf929ce8a3022 +R deb225722300c546330ebf788b8e9af6 +U drh +Z 08cfed6172a4eae5ba9e30ba4a2a4eaf # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 193d5d1547..8f996a05e6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f92a9bbabebc33a771835e259b04a57d07add1eacf3d9aec5fddf929ce8a3022 \ No newline at end of file +e21cf2e8f13465dbff33f9c21580752c1d8c077d6a253f56f04dab0d47eb99b8 \ No newline at end of file From 888e14f551afaf3d29ada200c8dff715342e8db3 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 18:02:02 +0000 Subject: [PATCH 014/148] Fix an incorrect file path in ext/jni/README.md FossilOrigin-Name: bcefa2df563260933c7ab5df90872580f71010c11419f6b1de7b1e2747237ff8 --- ext/jni/README.md | 2 +- manifest | 14 +++++++------- manifest.uuid | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index e1a91507cd..24296a7174 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -203,7 +203,7 @@ one of its three subclasses: - `SQLFunction.Window` implements window functions using four callbacks. -Search [`Tester1.java`](/file/src/org/sqlite/jni/Tester1.java) for +Search [`Tester1.java`](/file/ext/jni/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 diff --git a/manifest b/manifest index e481249598..9b1d00f24c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sminor\stypos\sin\sthe\sJNI\sREADME.md\sfile. -D 2023-07-28T15:58:09.824 +C Fix\san\sincorrect\sfile\spath\sin\sext/jni/README.md +D 2023-07-28T18:02:02.238 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d -F ext/jni/README.md ad1f948f0681951e516c667a68f4c195019100b271470fa8728e7c439b53bb84 +F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f F ext/jni/src/c/sqlite3-jni.c 9d0d58f3633bd8f467f893f45548873ed2c5451c673b0782b3cc6bfa92327b10 F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f92a9bbabebc33a771835e259b04a57d07add1eacf3d9aec5fddf929ce8a3022 -R deb225722300c546330ebf788b8e9af6 -U drh -Z 08cfed6172a4eae5ba9e30ba4a2a4eaf +P e21cf2e8f13465dbff33f9c21580752c1d8c077d6a253f56f04dab0d47eb99b8 +R c9d00a4dcbaf6ac6c795d4a5ee33d6c2 +U stephan +Z 889f38fca7122981dd02f9cd0f38c680 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8f996a05e6..e82f53007a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e21cf2e8f13465dbff33f9c21580752c1d8c077d6a253f56f04dab0d47eb99b8 \ No newline at end of file +bcefa2df563260933c7ab5df90872580f71010c11419f6b1de7b1e2747237ff8 \ No newline at end of file From 33c8e9d2331d6df690c01db186453b92e5070c70 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 28 Jul 2023 18:44:11 +0000 Subject: [PATCH 015/148] Internal JNI binding docs. FossilOrigin-Name: 991c66197e4dc7297fce3b20a1b4846873bcd4ce8add36aac71bd2e0e73c207b --- ext/jni/src/c/sqlite3-jni.c | 64 ++++++++++++++++++++++--------------- manifest | 12 +++---- manifest.uuid | 2 +- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 42c46b755b..25739904d5 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -147,7 +147,7 @@ JFuncName(Suffix) /* First 2 parameters to all JNI bindings. */ #define JENV_JSELF JNIEnv * env, jobject jSelf -/* Helper to squelch -Xcheck:jni warnings about +/* Helpers to squelch -Xcheck:jni warnings about not having checked for exceptions. */ #define IFTHREW if((*env)->ExceptionCheck(env)) #define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) @@ -157,10 +157,14 @@ #define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR +/** Helpers for extracting pointers from jobjects, noting that the + corresponding Java interfaces have already done the type-checking. + */ #define PtrGet_sqlite3(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3) #define PtrGet_sqlite3_stmt(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_stmt) #define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_value) #define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_context) +/* Helpers for Java value reference management. */ #define REF_G(VAR) (*env)->NewGlobalRef(env, VAR) /*#define REF_L(VAR) (*env)->NewLocalRef(env, VAR)*/ #define UNREF_G(VAR) if(VAR) (*env)->DeleteGlobalRef(env, (VAR)) @@ -190,14 +194,15 @@ static const struct { return (jint)CName(); \ } +/** Create a trivial JNI wrapper for (int CName(int)). */ #define WRAP_INT_INT(JniNameSuffix,CName) \ JDECL(jint,JniNameSuffix)(JNIEnv *env, jobject jSelf, jint arg){ \ return (jint)CName((int)arg); \ } /** Create a trivial JNI wrapper for (const mutf8_string * - CName(void)). This is only value for functions which are known to - return ASCII or text compatible with Modified UTF8. */ + CName(void)). This is only valid for functions which are known to + return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ #define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ JDECL(jstring,JniNameSuffix)(JENV_JSELF){ \ return (*env)->NewStringUTF( env, CName() ); \ @@ -209,16 +214,6 @@ static const struct { EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ return rc; \ } -/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ -#define WRAP_INT_DB(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ - return (jint)CName(PtrGet_sqlite3(pDb)); \ - } -/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ -#define WRAP_INT64_DB(JniNameSuffix,CName) \ - JDECL(jlong,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ - return (jlong)CName(PtrGet_sqlite3(pDb)); \ - } /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ #define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pStmt, jint n){ \ @@ -229,6 +224,16 @@ static const struct { JDECL(jstring,JniNameSuffix)(JENV_JSELF, jobject pStmt, jint ndx){ \ return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ } +/** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ +#define WRAP_INT_DB(JniNameSuffix,CName) \ + JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ + return (jint)CName(PtrGet_sqlite3(pDb)); \ + } +/** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ +#define WRAP_INT64_DB(JniNameSuffix,CName) \ + JDECL(jlong,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ + return (jlong)CName(PtrGet_sqlite3(pDb)); \ + } /** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ #define WRAP_INT_SVALUE(JniNameSuffix,CName) \ JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject jpSValue){ \ @@ -248,7 +253,7 @@ enum { /** Size of the per-JNIEnv cache. We have no way of knowing how many distinct JNIEnv's will be used in any given run, but know that it - will normally be only 1. Perhaps (just speculating) differen + will normally be only 1. Perhaps (just speculating) different threads use separate JNIEnvs? If that's the case, we don't(?) have enough info to evict from the cache when those JNIEnvs expire. @@ -261,26 +266,33 @@ enum { Need enough space for (only) the library's NativePointerHolder types, a fixed count known at build-time. If we add more than this a fatal error will be triggered with a reminder to increase this. + This value needs to be at least the number of entries in the + ClassNames object, as that value is our upper limit. The + ClassNames entries are the keys for this particular cache. */ - NphCache_SIZE = 10 + NphCache_SIZE = sizeof(ClassNames) / sizeof(char const *) }; /** - Cache for NativePointerHolder lookups. + Cache entry for NativePointerHolder lookups. */ typedef struct NphCacheLine NphCacheLine; struct NphCacheLine { - const char * zClassName /* "full/class/Name" */; - jclass klazz /* global ref to concrete NPH class */; - jmethodID midSet /* setNativePointer() */; - jmethodID midGet /* getNativePointer() */; - jmethodID midCtor /* constructor */; - jfieldID fidSetAgg /* sqlite3_context::aggregateContext */; + const char * zClassName /* "full/class/Name". Must be a static string + from the ClassNames struct. */; + jclass klazz /* global ref to concrete NativePointerHolder class */; + jmethodID midCtor /* klazz's constructor */; + jmethodID midSet /* NativePointerHolder.setNativePointer() */; + jmethodID midGet /* NativePointerHolder.getNativePointer() */; + jfieldID fidSetAgg /* sqlite3_context::aggregateContext */; }; +/** + Cache for per-JNIEnv data. +*/ typedef struct JNIEnvCacheLine JNIEnvCacheLine; struct JNIEnvCacheLine { - JNIEnv *env; + JNIEnv *env /* env in which this cache entry was created */; jclass globalClassObj /* global ref to java.lang.Object */; jclass globalClassLong /* global ref to java.lang.Long */; jmethodID ctorLong1 /* the Long(long) constructor */; @@ -298,7 +310,7 @@ static void NphCacheLine_clear(JNIEnv *env, NphCacheLine * p){ } static void JNIEnvCacheLine_clear(JNIEnvCacheLine * p){ - JNIEnv *env = p->env; + JNIEnv * const env = p->env; int i; if(env){ UNREF_G(p->globalClassObj); @@ -323,9 +335,9 @@ static void JNIEnvCache_clear(JNIEnvCache * p){ */ typedef struct { JNIEnv * env; /* env registered from */; - jobject jObj /* BusyHandlerJni instance */; + jobject jObj /* BusyHandler instance */; jclass klazz /* jObj's class */; - jmethodID jmidxCallback; + jmethodID jmidxCallback /* klazz's xCallback method */; } BusyHandlerJni; diff --git a/manifest b/manifest index 9b1d00f24c..1d9b4e8b26 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\san\sincorrect\sfile\spath\sin\sext/jni/README.md -D 2023-07-28T18:02:02.238 +C Internal\sJNI\sbinding\sdocs. +D 2023-07-28T18:44:11.596 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f -F ext/jni/src/c/sqlite3-jni.c 9d0d58f3633bd8f467f893f45548873ed2c5451c673b0782b3cc6bfa92327b10 +F ext/jni/src/c/sqlite3-jni.c f7f4934cb2698c674842974c1b47d0cba0927ebc5781b0c15304b7ed6c46b743 F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P e21cf2e8f13465dbff33f9c21580752c1d8c077d6a253f56f04dab0d47eb99b8 -R c9d00a4dcbaf6ac6c795d4a5ee33d6c2 +P bcefa2df563260933c7ab5df90872580f71010c11419f6b1de7b1e2747237ff8 +R 934bdd7c3796e2624766f5d43e958073 U stephan -Z 889f38fca7122981dd02f9cd0f38c680 +Z 1f7372379f92cca45c2da16bb8bc1a04 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e82f53007a..edc4bc0e92 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bcefa2df563260933c7ab5df90872580f71010c11419f6b1de7b1e2747237ff8 \ No newline at end of file +991c66197e4dc7297fce3b20a1b4846873bcd4ce8add36aac71bd2e0e73c207b \ No newline at end of file From ca379859c9bf10d25f05263d0903e8796250bfe3 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 04:31:56 +0000 Subject: [PATCH 016/148] Bind sqlite3_progress_handler() to JNI. Add some metrics to Tester1.java. FossilOrigin-Name: 437ecfe8abf8d294d429d191d811da6148e0b2ebb74cf66998480bfc8ef58bdf --- ext/jni/src/c/sqlite3-jni.c | 67 ++++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 8 +++ .../src/org/sqlite/jni/ProgressHandler.java | 2 + ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 4 +- ext/jni/src/org/sqlite/jni/Tester1.java | 57 +++++++++++++--- manifest | 20 +++--- manifest.uuid | 2 +- 7 files changed, 125 insertions(+), 35 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 25739904d5..72cfaf209e 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -433,11 +433,11 @@ static void s3jni_free(void * p){ */ static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ if( db!=0 ){ - if( 0!=zMsg ){ + if( 0==zMsg ){ + sqlite3Error(db, err_code); + }else{ const int nMsg = sqlite3Strlen30(zMsg); sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); - }else{ - sqlite3ErrorWithMsg(db, err_code, NULL); } } return err_code; @@ -526,7 +526,7 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb){ Clears s's state and moves it to the free-list. */ FIXME_THREADING -static void PerDbStateJni_set_aside(PerDbStateJni *s){ +static void PerDbStateJni_set_aside(PerDbStateJni * const s){ if(s){ JNIEnv * const env = s->env; assert(s->pDb && "Else this object is already in the free-list."); @@ -1726,6 +1726,50 @@ JDECL(jint,1prepare_1v3)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSq prepFlags, outStmt, outTail); } + +static int s3jni_progress_handler_impl(void *pP){ + PerDbStateJni * const ps = (PerDbStateJni *)pP; + JNIEnv * const env = ps->env; + int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, + ps->progress.midCallback); + IFTHREW{ + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "sqlite3_progress_handler() callback threw."); + } + return rc; +} + +JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + PerDbStateJni * ps = PerDbStateJni_for_db(env, pDb, 1); + jclass klazz; + if( n<1 || !jProgress ){ + if(ps){ + UNREF_G(ps->progress.jObj); + memset(&ps->progress, 0, sizeof(ps->progress)); + } + sqlite3_progress_handler(pDb, 0, 0, 0); + return; + } + if(!ps){ + s3jni_db_error(pDb, SQLITE_NOMEM, 0); + return; + } + klazz = (*env)->GetObjectClass(env, jProgress); + ps->progress.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on " + "ProgressHandler object."); + }else{ + ps->progress.jObj = REF_G(jProgress); + sqlite3_progress_handler(pDb, (int)n, s3jni_progress_handler_impl, ps); + } +} + + /* sqlite3_result_text/blob() and friends. */ static void result_blob_text(int asBlob, int as64, int eTextRep/*only for (asBlob=0)*/, @@ -1945,30 +1989,31 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ UNREF_L(jX); IFTHREW{ EXCEPTION_CLEAR; - return rc ? rc : - s3jni_db_error(ps->pDb, SQLITE_ERROR, - "sqlite3_trace_v2() callback threw."); + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "sqlite3_trace_v2() callback threw."); } return rc; } JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * ps; + PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); jclass klazz; if( !traceMask || !jTracer ){ + if(ps){ + UNREF_G(ps->trace.jObj); + memset(&ps->trace, 0, sizeof(ps->trace)); + } return (jint)sqlite3_trace_v2(pDb, 0, 0, 0); } - ps = PerDbStateJni_for_db(env, pDb, 1); if(!ps) return SQLITE_NOMEM; klazz = (*env)->GetObjectClass(env, jTracer); ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "(IJLjava/lang/Object;)I"); IFTHREW { - /* Leave ps in place - it might contain other state. */ EXCEPTION_CLEAR; return s3jni_db_error(pDb, SQLITE_ERROR, - "Cannot not find matchin xCallback() on Tracer object."); + "Cannot not find matching xCallback() on Tracer object."); } ps->trace.jObj = REF_G(jTracer); return sqlite3_trace_v2(pDb, (unsigned)traceMask, s3jni_trace_impl, ps); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 524a9b4075..739aea7a87 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1195,6 +1195,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_progress_handler + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/ProgressHandler;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler + (JNIEnv *, jclass, jobject, jint, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_reset diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/ProgressHandler.java index b3e8abc0c0..8c463fe364 100644 --- a/ext/jni/src/org/sqlite/jni/ProgressHandler.java +++ b/ext/jni/src/org/sqlite/jni/ProgressHandler.java @@ -18,6 +18,8 @@ package org.sqlite.jni; */ public interface ProgressHandler { /** + Works as documented for the sqlite3_progress_handler() callback. + Must not throw. */ int xCallback(); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index eabe1c8609..e3162d7967 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -394,8 +394,8 @@ public final class SQLite3Jni { return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } - - //TODO public static native void sqlite3_progress_handler(sqlite3 db, int n, ProgressHandler h); + public static native void sqlite3_progress_handler(@NotNull sqlite3 db, int n, + @Nullable ProgressHandler h); public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index b2772cc48b..7cc8815436 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -17,6 +17,11 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; public class Tester1 { + private static final class Metrics { + int dbOpen; + } + + static final Metrics metrics = new Metrics(); private static void out(T val){ System.out.print(val); @@ -87,9 +92,10 @@ public class Tester1 { sqlite3 db = new sqlite3(); affirm(0 == db.getNativePointer()); int rc = sqlite3_open(":memory:", db); + ++metrics.dbOpen; affirm(0 == rc); affirm(0 < db.getNativePointer()); - sqlite3_close(db); + sqlite3_close_v2(db); affirm(0 == db.getNativePointer()); } @@ -99,6 +105,7 @@ public class Tester1 { int rc = sqlite3_open_v2(":memory:", db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null); + ++metrics.dbOpen; affirm(0 == rc); affirm(0 < db.getNativePointer()); sqlite3_close_v2(db); @@ -109,6 +116,7 @@ public class Tester1 { sqlite3 db = new sqlite3(); affirm(0 == db.getNativePointer()); int rc = sqlite3_open(":memory:", db); + ++metrics.dbOpen; affirm(0 == rc); affirm(0 != db.getNativePointer()); rc = sqlite3_busy_timeout(db, 2000); @@ -394,7 +402,7 @@ public class Tester1 { affirm(3 == counter); sqlite3_finalize(stmt); affirm(!xDestroyCalled.value); - sqlite3_close(db); + sqlite3_close_v2(db); affirm(xDestroyCalled.value); } @@ -448,7 +456,7 @@ public class Tester1 { execSql(db, "SELECT myfunc(1,2,3)"); affirm(6 == xFuncAccum.value); affirm( !xDestroyCalled.value ); - sqlite3_close(db); + sqlite3_close_v2(db); affirm( xDestroyCalled.value ); } @@ -477,7 +485,7 @@ public class Tester1 { } sqlite3_finalize(stmt); affirm( 1 == n ); - sqlite3_close(db); + sqlite3_close_v2(db); } private static void testUdfAggregate(){ @@ -543,7 +551,7 @@ public class Tester1 { execSql(db, "SELECT myfunc(1) WHERE 0"); affirm(xFinalNull.value); - sqlite3_close(db); + sqlite3_close_v2(db); } private static void testUdfWindow(){ @@ -601,7 +609,7 @@ public class Tester1 { } sqlite3_finalize(stmt); affirm( 5 == n ); - sqlite3_close(db); + sqlite3_close_v2(db); } private static void listBoundMethods(){ @@ -664,7 +672,7 @@ public class Tester1 { }); execSql(db, "SELECT 1; SELECT 2"); affirm( 6 == counter.value ); - sqlite3_close(db); + sqlite3_close_v2(db); affirm( 7 == counter.value ); } @@ -674,9 +682,11 @@ public class Tester1 { final sqlite3 db2 = new sqlite3(); int rc = sqlite3_open(dbName, db1); + ++metrics.dbOpen; affirm( 0 == rc ); execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); rc = sqlite3_open(dbName, db2); + ++metrics.dbOpen; affirm( 0 == rc ); final ValueHolder xDestroyed = new ValueHolder<>(false); @@ -701,9 +711,9 @@ public class Tester1 { affirm( SQLITE_BUSY == rc); affirm( 3 == xBusyCalled.value ); sqlite3_finalize(stmt); - sqlite3_close(db1); + sqlite3_close_v2(db1); affirm(!xDestroyed.value); - sqlite3_close(db2); + sqlite3_close_v2(db2); affirm(xDestroyed.value); try{ final java.io.File f = new java.io.File(dbName); @@ -713,6 +723,24 @@ public class Tester1 { } } + private static void testProgress(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + sqlite3_progress_handler(db, 1, new ProgressHandler(){ + public int xCallback(){ + ++counter.value; + return 0; + } + }); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( counter.value > 0 ); + int nOld = counter.value; + sqlite3_progress_handler(db, 0, null); + execSql(db, "SELECT 1; SELECT 2;"); + affirm( nOld == counter.value ); + sqlite3_close_v2(db); + } + private static void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); @@ -720,6 +748,7 @@ public class Tester1 { } public static void main(String[] args){ + final long timeStart = System.nanoTime(); test1(); if(false) testCompileOption(); final java.util.List liArgs = @@ -740,10 +769,16 @@ public class Tester1 { testUdfWindow(); testTrace(); testBusy(); - testSleep(); + testProgress(); + //testSleep(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); } - outln("Tests done. "+affirmCount+" assertion checked."); + final long timeEnd = System.nanoTime(); + outln("Tests done. Metrics:"); + outln("\tAssertions checked: "+affirmCount); + outln("\tDatabases opened: "+metrics.dbOpen); + outln("\tTotal time = " + +((timeEnd - timeStart)/1000000.0)+"ms"); } } diff --git a/manifest b/manifest index 1d9b4e8b26..0ccad35a87 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sJNI\sbinding\sdocs. -D 2023-07-28T18:44:11.596 +C Bind\ssqlite3_progress_handler()\sto\sJNI.\sAdd\ssome\smetrics\sto\sTester1.java. +D 2023-07-30T04:31:56.673 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,16 +232,16 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f -F ext/jni/src/c/sqlite3-jni.c f7f4934cb2698c674842974c1b47d0cba0927ebc5781b0c15304b7ed6c46b743 -F ext/jni/src/c/sqlite3-jni.h c9bb150a38dce09cc2794d5aac8fa097288d9946fbb15250fd0a23c31957f506 +F ext/jni/src/c/sqlite3-jni.c 53fa705d417ab61f4879566352bd1b3e31996aa01d657d6beca597734bff1d0e +F ext/jni/src/c/sqlite3-jni.h 212c3aede7c9ca8b1116a5d822cb89b95b3d7912452dbe778a9fd6cb869607e3 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c 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/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f7939d75d26481d02ea3418fa24bb6f6ba89b98a3e10ac4ab67b7186f9cb4fda -F ext/jni/src/org/sqlite/jni/Tester1.java 6c819172137bf06d76f26df20c4c846abc82523db62c51cc043ac66031298103 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5d57e46e9d2a1c50d20cbe890908026252068a67ce61dfbb55f23d867dc1d827 +F ext/jni/src/org/sqlite/jni/Tester1.java 69035e38d68ae18d6cc179a82094d9450e6175c973c19969264d48c0e75d6951 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 @@ -2067,8 +2067,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P bcefa2df563260933c7ab5df90872580f71010c11419f6b1de7b1e2747237ff8 -R 934bdd7c3796e2624766f5d43e958073 +P 991c66197e4dc7297fce3b20a1b4846873bcd4ce8add36aac71bd2e0e73c207b +R 3db7b0c753b00fafc33e16210b0ec893 U stephan -Z 1f7372379f92cca45c2da16bb8bc1a04 +Z b93e8aa0e181c83baf410d69abe8fc02 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index edc4bc0e92..2fd5dd978a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -991c66197e4dc7297fce3b20a1b4846873bcd4ce8add36aac71bd2e0e73c207b \ No newline at end of file +437ecfe8abf8d294d429d191d811da6148e0b2ebb74cf66998480bfc8ef58bdf \ No newline at end of file From f52de0fdafa719ef6d6a61b78cb1a2893b8e5e0a Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 05:50:34 +0000 Subject: [PATCH 017/148] Bind sqlite3_commit_hook() to JNI. FossilOrigin-Name: c687297fcae082cbd7f9258c43f3841fd34904d8b62b0adf3cd61fcddeee483d --- ext/jni/src/c/sqlite3-jni.c | 66 +++++++++++++++++++++- ext/jni/src/c/sqlite3-jni.h | 8 +++ ext/jni/src/org/sqlite/jni/CommitHook.java | 25 ++++++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 14 ++--- ext/jni/src/org/sqlite/jni/Tester1.java | 45 +++++++++++++++ manifest | 19 ++++--- manifest.uuid | 2 +- 7 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/CommitHook.java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 72cfaf209e..9d923a5393 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -166,7 +166,7 @@ #define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_context) /* Helpers for Java value reference management. */ #define REF_G(VAR) (*env)->NewGlobalRef(env, VAR) -/*#define REF_L(VAR) (*env)->NewLocalRef(env, VAR)*/ +#define REF_L(VAR) (*env)->NewLocalRef(env, VAR) #define UNREF_G(VAR) if(VAR) (*env)->DeleteGlobalRef(env, (VAR)) #define UNREF_L(VAR) if(VAR) (*env)->DeleteLocalRef(env, (VAR)) @@ -1512,6 +1512,65 @@ JDECL(jobject,1column_1value)(JENV_JSELF, jobject jpStmt, return new_sqlite3_value_wrapper(env, sv); } +static int s3jni_commit_hook_impl(void *pP){ + PerDbStateJni * const ps = (PerDbStateJni *)pP; + JNIEnv * const env = ps->env; + int rc = (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, + ps->commitHook.midCallback); + IFTHREW{ + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, + "sqlite3_commit_hook() callback threw."); + } + return rc; +} + +JDECL(jobject,1commit_1hook)(JENV_JSELF,jobject jDb, jobject jCommitHook){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + PerDbStateJni * ps = PerDbStateJni_for_db(env, pDb, 1); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + if(!ps){ + s3jni_db_error(pDb, SQLITE_NOMEM, 0); + return 0; + } + pOld = ps->commitHook.jObj; + if(pOld && jCommitHook && + (*env)->IsSameObject(env, pOld, jCommitHook)){ + return pOld; + } + if( !jCommitHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(&ps->commitHook, 0, sizeof(ps->commitHook)); + sqlite3_commit_hook(pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jCommitHook); + xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(pDb, SQLITE_ERROR, + "Cannot not find matching xCallback() on " + "CommitHook object."); + }else{ + ps->commitHook.midCallback = xCallback; + ps->commitHook.jObj = REF_G(jCommitHook); + sqlite3_commit_hook(pDb, s3jni_commit_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + + JDECL(jstring,1compileoption_1get)(JENV_JSELF, jint n){ return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); } @@ -1744,6 +1803,7 @@ JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress sqlite3 * const pDb = PtrGet_sqlite3(jDb); PerDbStateJni * ps = PerDbStateJni_for_db(env, pDb, 1); jclass klazz; + jmethodID xCallback; if( n<1 || !jProgress ){ if(ps){ UNREF_G(ps->progress.jObj); @@ -1757,13 +1817,15 @@ JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress return; } klazz = (*env)->GetObjectClass(env, jProgress); - ps->progress.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); IFTHREW { EXCEPTION_CLEAR; s3jni_db_error(pDb, SQLITE_ERROR, "Cannot not find matching xCallback() on " "ProgressHandler object."); }else{ + UNREF_G(ps->progress.jObj); + ps->progress.midCallback = xCallback; ps->progress.jObj = REF_G(jProgress); sqlite3_progress_handler(pDb, (int)n, s3jni_progress_handler_impl, ps); } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 739aea7a87..11f0f99e91 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1027,6 +1027,14 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1context_1db_1handle (JNIEnv *, jclass, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_commit_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CommitHook;)Lorg/sqlite/jni/CommitHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1commit_1hook + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_compileoption_get diff --git a/ext/jni/src/org/sqlite/jni/CommitHook.java b/ext/jni/src/org/sqlite/jni/CommitHook.java new file mode 100644 index 0000000000..c72d2df9b3 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/CommitHook.java @@ -0,0 +1,25 @@ +/* +** 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_commit_hook(). +*/ +public interface CommitHook { + /** + Works as documented for the sqlite3_commit_hook() callback. + Must not throw. + */ + int xCallback(); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index e3162d7967..7bcfb70e88 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -243,7 +243,7 @@ public final class SQLite3Jni { public static native sqlite3 sqlite3_context_db_handle(@NotNull sqlite3_context cx); - //TODO? void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); + public static native CommitHook sqlite3_commit_hook(@NotNull sqlite3 db, @Nullable CommitHook hook); public static native String sqlite3_compileoption_get(int n); @@ -287,8 +287,6 @@ public final class SQLite3Jni { public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); - //TODO? void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); - public static native String sqlite3_libversion(); public static native int sqlite3_libversion_number(); @@ -397,6 +395,8 @@ public final class SQLite3Jni { public static native void sqlite3_progress_handler(@NotNull sqlite3 db, int n, @Nullable ProgressHandler h); + //TODO??? void *sqlite3_preupdate_hook(...) and friends + public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); public static native void sqlite3_result_double(@NotNull sqlite3_context cx, double v); @@ -631,11 +631,7 @@ public final class SQLite3Jni { sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); } - /** - public static void sqlite3_result_text64(@NotNull sqlite3_context cx, - @Nullable byte[] text){ - sqlite3_result_text64(cx, text, null==text ? 0 : text.length, SQLITE_UTF8); - }**/ + //TODO void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); public static native void sqlite3_set_last_insert_rowid(@NotNull sqlite3 db, long rowid); @@ -664,6 +660,8 @@ public final class SQLite3Jni { public static native int sqlite3_trace_v2(@NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer); + //TODO void *sqlite3_update_hook(sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void*); + public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); public static native int sqlite3_value_bytes(@NotNull sqlite3_value v); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 7cc8815436..f753a22e4a 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -741,6 +741,50 @@ public class Tester1 { sqlite3_close_v2(db); } + private static void testCommitHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final CommitHook theHook = new CommitHook(){ + public int xCallback(){ + ++counter.value; + return 0; + } + }; + CommitHook oldHook = sqlite3_commit_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 2 == counter.value ); + execSql(db, "BEGIN; SELECT 1; SELECT 2; COMMIT;"); + affirm( 2 == counter.value /* NOT invoked if no changes are made */ ); + execSql(db, "BEGIN; update t set a='d' where a='c'; COMMIT;"); + affirm( 3 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='e' where a='d'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "BEGIN; update t set a='f' where a='e'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, null); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='g' where a='f'; COMMIT;"); + affirm( 4 == counter.value ); + + final CommitHook newHook = new CommitHook(){ + public int xCallback(){return 0;} + }; + oldHook = sqlite3_commit_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "BEGIN; update t set a='h' where a='g'; COMMIT;"); + affirm( 4 == counter.value ); + oldHook = sqlite3_commit_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); + affirm( 5 == counter.value ); + sqlite3_close_v2(db); + } + private static void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); @@ -770,6 +814,7 @@ public class Tester1 { testTrace(); testBusy(); testProgress(); + testCommitHook(); //testSleep(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); diff --git a/manifest b/manifest index 0ccad35a87..cff3fdf576 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_progress_handler()\sto\sJNI.\sAdd\ssome\smetrics\sto\sTester1.java. -D 2023-07-30T04:31:56.673 +C Bind\ssqlite3_commit_hook()\sto\sJNI. +D 2023-07-30T05:50:34.120 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,16 +232,17 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f -F ext/jni/src/c/sqlite3-jni.c 53fa705d417ab61f4879566352bd1b3e31996aa01d657d6beca597734bff1d0e -F ext/jni/src/c/sqlite3-jni.h 212c3aede7c9ca8b1116a5d822cb89b95b3d7912452dbe778a9fd6cb869607e3 +F ext/jni/src/c/sqlite3-jni.c bbc85b4bfe8ea05be23771790824f179b17106a806b2507a6b09c2710483f758 +F ext/jni/src/c/sqlite3-jni.h 2d6ec0b98c94fa111b101aa25c94dbc86aa8776e96048c27cf9d646e9aa2f68f F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 +F ext/jni/src/org/sqlite/jni/CommitHook.java a2e60ef94c56b9c5058130eaeadf98e6121d28f1828000e5e998397a0c3dde74 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 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5d57e46e9d2a1c50d20cbe890908026252068a67ce61dfbb55f23d867dc1d827 -F ext/jni/src/org/sqlite/jni/Tester1.java 69035e38d68ae18d6cc179a82094d9450e6175c973c19969264d48c0e75d6951 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java ca239543968a4fd229322b92925a2754358964be6b1fc4099ff548372db2009e +F ext/jni/src/org/sqlite/jni/Tester1.java f7144f586b10e7cd42974c7cb067d7e026a0953612a8ca9fcf950b7e6f636bbf 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 @@ -2067,8 +2068,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 991c66197e4dc7297fce3b20a1b4846873bcd4ce8add36aac71bd2e0e73c207b -R 3db7b0c753b00fafc33e16210b0ec893 +P 437ecfe8abf8d294d429d191d811da6148e0b2ebb74cf66998480bfc8ef58bdf +R f8d9020a913f8fd8756147b3f4685626 U stephan -Z b93e8aa0e181c83baf410d69abe8fc02 +Z 4f8f049f3a39067bd46eaa81ec3fdaf7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2fd5dd978a..844332b6b5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -437ecfe8abf8d294d429d191d811da6148e0b2ebb74cf66998480bfc8ef58bdf \ No newline at end of file +c687297fcae082cbd7f9258c43f3841fd34904d8b62b0adf3cd61fcddeee483d \ No newline at end of file From 00c0c5883adc10f7655bbf4de527d53dad62e282 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 06:00:53 +0000 Subject: [PATCH 018/148] Add commit hook failure to JNI tests. FossilOrigin-Name: be4459b495cc2555e4d8ca24dd56c3da1036d25af7cc077bbc3d93c2a3e8c40b --- ext/jni/src/org/sqlite/jni/Tester1.java | 23 +++++++++++++++++++---- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index f753a22e4a..2c3c6bae8e 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -64,19 +64,20 @@ public class Tester1 { private static void execSql(sqlite3 db, String[] sql){ execSql(db, String.join("", sql)); } - private static void execSql(sqlite3 db, String sql){ + private static int execSql(sqlite3 db, boolean throwOnError, 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(); + int rc = 0; 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); + rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); affirm(0 == rc); pos = oTail.getValue(); affirm(0 != stmt.getNativePointer()); @@ -84,9 +85,18 @@ public class Tester1 { sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer()); if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ - throw new RuntimeException("db op failed with rc="+rc); + if(throwOnError){ + throw new RuntimeException("db op failed with rc="+rc); + }else{ + break; + } } } + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + return rc; + } + private static void execSql(sqlite3 db, String sql){ + execSql(db, true, sql); } private static void testOpenDb1(){ sqlite3 db = new sqlite3(); @@ -744,10 +754,11 @@ public class Tester1 { private static void testCommitHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder hookResult = new ValueHolder<>(0); final CommitHook theHook = new CommitHook(){ public int xCallback(){ ++counter.value; - return 0; + return hookResult.value; } }; CommitHook oldHook = sqlite3_commit_hook(db, theHook); @@ -782,6 +793,10 @@ public class Tester1 { affirm( newHook == oldHook ); execSql(db, "BEGIN; update t set a='i' where a='h'; COMMIT;"); affirm( 5 == counter.value ); + hookResult.value = SQLITE_ERROR; + int rc = execSql(db, false, "BEGIN; update t set a='j' where a='i'; COMMIT;"); + affirm( SQLITE_CONSTRAINT == rc ); + affirm( 6 == counter.value ); sqlite3_close_v2(db); } diff --git a/manifest b/manifest index cff3fdf576..974a60d6e5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_commit_hook()\sto\sJNI. -D 2023-07-30T05:50:34.120 +C Add\scommit\shook\sfailure\sto\sJNI\stests. +D 2023-07-30T06:00:53.315 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -242,7 +242,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 08a752b58a33696c5eaf0eb9361a0966 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java ca239543968a4fd229322b92925a2754358964be6b1fc4099ff548372db2009e -F ext/jni/src/org/sqlite/jni/Tester1.java f7144f586b10e7cd42974c7cb067d7e026a0953612a8ca9fcf950b7e6f636bbf +F ext/jni/src/org/sqlite/jni/Tester1.java 76bca3c7a37019d2631aa224d23edb814c9be322db0529b2fd7cea5d52a202d6 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 @@ -2068,8 +2068,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 437ecfe8abf8d294d429d191d811da6148e0b2ebb74cf66998480bfc8ef58bdf -R f8d9020a913f8fd8756147b3f4685626 +P c687297fcae082cbd7f9258c43f3841fd34904d8b62b0adf3cd61fcddeee483d +R f495472e9e17ebb6a754de3bf1538a92 U stephan -Z 4f8f049f3a39067bd46eaa81ec3fdaf7 +Z cadc90f478a05b2b1a81a1b897b5c5ad # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 844332b6b5..ff969cfbd7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c687297fcae082cbd7f9258c43f3841fd34904d8b62b0adf3cd61fcddeee483d \ No newline at end of file +be4459b495cc2555e4d8ca24dd56c3da1036d25af7cc077bbc3d93c2a3e8c40b \ No newline at end of file From b162c68d6241749655991e617c916da7e8b207ca Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 06:44:21 +0000 Subject: [PATCH 019/148] Bind sqlite3_rollback_hook() to JNI. FossilOrigin-Name: 5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee --- ext/jni/src/c/sqlite3-jni.c | 91 ++++++++++++-------- ext/jni/src/c/sqlite3-jni.h | 8 ++ ext/jni/src/org/sqlite/jni/CommitHook.java | 2 +- ext/jni/src/org/sqlite/jni/RollbackHook.java | 25 ++++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 3 +- ext/jni/src/org/sqlite/jni/Tester1.java | 37 +++++++- manifest | 21 ++--- manifest.uuid | 2 +- 8 files changed, 137 insertions(+), 52 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/RollbackHook.java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 9d923a5393..4daa1d104c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -340,6 +340,12 @@ typedef struct { jmethodID jmidxCallback /* klazz's xCallback method */; } BusyHandlerJni; +typedef struct JniHookState JniHookState; +struct JniHookState{ + jobject jObj; + jmethodID midCallback; +}; + /** Per-(sqlite3*) state for bindings which do not have their own @@ -359,22 +365,10 @@ struct PerDbStateJni { sqlite3 * pDb; PerDbStateJni * pNext; PerDbStateJni * pPrev; - struct { - jobject jObj; - jmethodID midCallback; - } trace; - struct { - jobject jObj; - jmethodID midCallback; - } progress; - struct { - jobject jObj; - jmethodID midCallback; - } commitHook; - struct { - jobject jObj; - jmethodID midCallback; - } rollbackHook; + JniHookState trace; + JniHookState progress; + JniHookState commitHook; + JniHookState rollbackHook; BusyHandlerJni busyHandler; }; @@ -1512,55 +1506,71 @@ JDECL(jobject,1column_1value)(JENV_JSELF, jobject jpStmt, return new_sqlite3_value_wrapper(env, sv); } -static int s3jni_commit_hook_impl(void *pP){ - PerDbStateJni * const ps = (PerDbStateJni *)pP; + +static int s3jni_commit_rollback_hook_impl(int isCommit, PerDbStateJni * const ps){ JNIEnv * const env = ps->env; - int rc = (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, - ps->commitHook.midCallback); + int rc = isCommit + ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, + ps->commitHook.midCallback) + : (int)((*env)->CallVoidMethod(env, ps->rollbackHook.jObj, + ps->rollbackHook.midCallback), 0); IFTHREW{ EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, - "sqlite3_commit_hook() callback threw."); + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "hook callback threw."); } return rc; } -JDECL(jobject,1commit_1hook)(JENV_JSELF,jobject jDb, jobject jCommitHook){ +static int s3jni_commit_hook_impl(void *pP){ + return s3jni_commit_rollback_hook_impl(1, pP); +} + +static void s3jni_rollback_hook_impl(void *pP){ + (void)s3jni_commit_rollback_hook_impl(0, pP); +} + +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, + jobject jHook){ sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * ps = PerDbStateJni_for_db(env, pDb, 1); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); jclass klazz; jobject pOld = 0; jmethodID xCallback; + JniHookState * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; if(!ps){ s3jni_db_error(pDb, SQLITE_NOMEM, 0); return 0; } - pOld = ps->commitHook.jObj; - if(pOld && jCommitHook && - (*env)->IsSameObject(env, pOld, jCommitHook)){ + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ return pOld; } - if( !jCommitHook ){ + if( !jHook ){ if(pOld){ jobject tmp = REF_L(pOld); UNREF_G(pOld); pOld = tmp; } - memset(&ps->commitHook, 0, sizeof(ps->commitHook)); - sqlite3_commit_hook(pDb, 0, 0); + memset(pHook, 0, sizeof(JniHookState)); + if( isCommit ) sqlite3_commit_hook(pDb, 0, 0); + else sqlite3_rollback_hook(pDb, 0, 0); return pOld; } - klazz = (*env)->GetObjectClass(env, jCommitHook); - xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, + isCommit ? "xCommitHook" : "xRollbackHook", + isCommit ? "()I" : "()V"); IFTHREW { EXCEPTION_CLEAR; s3jni_db_error(pDb, SQLITE_ERROR, - "Cannot not find matching xCallback() on " - "CommitHook object."); + "Cannot not find matching callback on " + "hook object."); }else{ - ps->commitHook.midCallback = xCallback; - ps->commitHook.jObj = REF_G(jCommitHook); - sqlite3_commit_hook(pDb, s3jni_commit_hook_impl, ps); + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + if( isCommit ) sqlite3_commit_hook(pDb, s3jni_commit_hook_impl, ps); + else sqlite3_rollback_hook(pDb, s3jni_rollback_hook_impl, ps); if(pOld){ jobject tmp = REF_L(pOld); UNREF_G(pOld); @@ -1570,6 +1580,10 @@ JDECL(jobject,1commit_1hook)(JENV_JSELF,jobject jDb, jobject jCommitHook){ return pOld; } +JDECL(jobject,1commit_1hook)(JENV_JSELF,jobject jDb, jobject jHook){ + return s3jni_commit_rollback_hook(1, env, jDb, jHook); +} + JDECL(jstring,1compileoption_1get)(JENV_JSELF, jint n){ return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); @@ -2007,6 +2021,9 @@ JDECL(jint,1result_1zeroblob64)(JENV_JSELF, jobject jpCx, jlong v){ return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); } +JDECL(jobject,1rollback_1hook)(JENV_JSELF,jobject jDb, jobject jHook){ + return s3jni_commit_rollback_hook(0, env, jDb, jHook); +} JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 11f0f99e91..f9fa4a92b1 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1347,6 +1347,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_rollback_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/RollbackHook;)Lorg/sqlite/jni/RollbackHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_set_last_insert_rowid diff --git a/ext/jni/src/org/sqlite/jni/CommitHook.java b/ext/jni/src/org/sqlite/jni/CommitHook.java index c72d2df9b3..eaa75a0040 100644 --- a/ext/jni/src/org/sqlite/jni/CommitHook.java +++ b/ext/jni/src/org/sqlite/jni/CommitHook.java @@ -21,5 +21,5 @@ public interface CommitHook { Works as documented for the sqlite3_commit_hook() callback. Must not throw. */ - int xCallback(); + int xCommitHook(); } diff --git a/ext/jni/src/org/sqlite/jni/RollbackHook.java b/ext/jni/src/org/sqlite/jni/RollbackHook.java new file mode 100644 index 0000000000..4ce3cb93e3 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/RollbackHook.java @@ -0,0 +1,25 @@ +/* +** 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_rollback_hook(). +*/ +public interface RollbackHook { + /** + Works as documented for the sqlite3_rollback_hook() callback. + Must not throw. + */ + void xRollbackHook(); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 7bcfb70e88..36b824a808 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -631,7 +631,8 @@ public final class SQLite3Jni { sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); } - //TODO void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); + public static native RollbackHook sqlite3_rollback_hook(@NotNull sqlite3 db, + @Nullable RollbackHook hook); public static native void sqlite3_set_last_insert_rowid(@NotNull sqlite3 db, long rowid); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 2c3c6bae8e..67e3bf639a 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -756,7 +756,7 @@ public class Tester1 { final ValueHolder counter = new ValueHolder<>(0); final ValueHolder hookResult = new ValueHolder<>(0); final CommitHook theHook = new CommitHook(){ - public int xCallback(){ + public int xCommitHook(){ ++counter.value; return hookResult.value; } @@ -783,7 +783,7 @@ public class Tester1 { affirm( 4 == counter.value ); final CommitHook newHook = new CommitHook(){ - public int xCallback(){return 0;} + public int xCommitHook(){return 0;} }; oldHook = sqlite3_commit_hook(db, newHook); affirm( null == oldHook ); @@ -800,6 +800,38 @@ public class Tester1 { sqlite3_close_v2(db); } + private static void testRollbackHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final RollbackHook theHook = new RollbackHook(){ + public void xRollbackHook(){ + ++counter.value; + } + }; + RollbackHook oldHook = sqlite3_rollback_hook(db, theHook); + affirm( null == oldHook ); + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 0 == counter.value ); + execSql(db, false, "BEGIN; SELECT 1; SELECT 2; ROLLBACK;"); + affirm( 1 == counter.value /* contra to commit hook, is invoked if no changes are made */ ); + + final RollbackHook newHook = new RollbackHook(){ + public void xRollbackHook(){return;} + }; + oldHook = sqlite3_rollback_hook(db, newHook); + affirm( theHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 1 == counter.value ); + oldHook = sqlite3_rollback_hook(db, theHook); + affirm( newHook == oldHook ); + execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 2 == counter.value ); + int rc = execSql(db, false, "BEGIN; SELECT 1; ROLLBACK;"); + affirm( 0 == rc ); + affirm( 3 == counter.value ); + sqlite3_close_v2(db); + } + private static void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); @@ -830,6 +862,7 @@ public class Tester1 { testBusy(); testProgress(); testCommitHook(); + testRollbackHook(); //testSleep(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); diff --git a/manifest b/manifest index 974a60d6e5..d9877eabbf 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\scommit\shook\sfailure\sto\sJNI\stests. -D 2023-07-30T06:00:53.315 +C Bind\ssqlite3_rollback_hook()\sto\sJNI. +D 2023-07-30T06:44:21.370 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,17 +232,18 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f -F ext/jni/src/c/sqlite3-jni.c bbc85b4bfe8ea05be23771790824f179b17106a806b2507a6b09c2710483f758 -F ext/jni/src/c/sqlite3-jni.h 2d6ec0b98c94fa111b101aa25c94dbc86aa8776e96048c27cf9d646e9aa2f68f +F ext/jni/src/c/sqlite3-jni.c 84c082bd07d130b3db4121c32429a9ea1e57c109366b78909b3a7bba4b9afd68 +F ext/jni/src/c/sqlite3-jni.h 58f1f1a363184348c56dd6af47a2311640dde61ab0d9f4ba51fadf6ae5ba88c3 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 -F ext/jni/src/org/sqlite/jni/CommitHook.java a2e60ef94c56b9c5058130eaeadf98e6121d28f1828000e5e998397a0c3dde74 +F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a 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 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc +F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java ca239543968a4fd229322b92925a2754358964be6b1fc4099ff548372db2009e -F ext/jni/src/org/sqlite/jni/Tester1.java 76bca3c7a37019d2631aa224d23edb814c9be322db0529b2fd7cea5d52a202d6 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1e0ec8a4a5f95b0f595666f9b92246f3394f31f78f62503ed1d1ab879a695101 +F ext/jni/src/org/sqlite/jni/Tester1.java 7d65095d4d4e683f937de57f9340e7eda966a9bd0adba945b86b76570a12c3a7 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 @@ -2068,8 +2069,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c687297fcae082cbd7f9258c43f3841fd34904d8b62b0adf3cd61fcddeee483d -R f495472e9e17ebb6a754de3bf1538a92 +P be4459b495cc2555e4d8ca24dd56c3da1036d25af7cc077bbc3d93c2a3e8c40b +R 6da75ca1ede9a6d1b13c3955b22a3d72 U stephan -Z cadc90f478a05b2b1a81a1b897b5c5ad +Z 04a18495036c22c78ae28b1e24549558 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ff969cfbd7..5d2b1e0838 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -be4459b495cc2555e4d8ca24dd56c3da1036d25af7cc077bbc3d93c2a3e8c40b \ No newline at end of file +5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee \ No newline at end of file From fecad503a9bfe76218e9d88b20f301e37a95a20a Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 07:44:03 +0000 Subject: [PATCH 020/148] Bind sqlite3_update_hook() to JNI. FossilOrigin-Name: a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde --- ext/jni/README.md | 6 ++ ext/jni/src/c/sqlite3-jni.c | 81 +++++++++++++++++++++- ext/jni/src/c/sqlite3-jni.h | 8 +++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 2 +- ext/jni/src/org/sqlite/jni/Tester1.java | 51 ++++++++++++++ ext/jni/src/org/sqlite/jni/UpdateHook.java | 25 +++++++ manifest | 21 +++--- manifest.uuid | 2 +- 8 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/UpdateHook.java diff --git a/ext/jni/README.md b/ext/jni/README.md index 24296a7174..d62eddf6a7 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -91,6 +91,12 @@ conversion is performed. In short, conversion in Java, and there is no JNI C API for that conversion (JNI's `NewStringUTF()` returns MUTF-8). +Known consequences and limitations of this discrepancy include: + +- Database and table names must not contain characters which differ + in MUTF-8 and UTF-8, or certain APIs will mis-translate them on + their way between languages. + [modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 4daa1d104c..85d010b304 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -340,13 +340,13 @@ typedef struct { jmethodID jmidxCallback /* klazz's xCallback method */; } BusyHandlerJni; +/** State for various hook callbacks. */ typedef struct JniHookState JniHookState; struct JniHookState{ jobject jObj; jmethodID midCallback; }; - /** Per-(sqlite3*) state for bindings which do not have their own finalizer functions, e.g. tracing and commit/rollback hooks. This @@ -369,6 +369,7 @@ struct PerDbStateJni { JniHookState progress; JniHookState commitHook; JniHookState rollbackHook; + JniHookState updateHook; BusyHandlerJni busyHandler; }; @@ -1562,6 +1563,8 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, isCommit ? "xCommitHook" : "xRollbackHook", isCommit ? "()I" : "()V"); IFTHREW { + MARKER(("WARNING: callback MUST NOT THROW.\n")); + EXCEPTION_REPORT; EXCEPTION_CLEAR; s3jni_db_error(pDb, SQLITE_ERROR, "Cannot not find matching callback on " @@ -2098,6 +2101,82 @@ JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ return sqlite3_trace_v2(pDb, (unsigned)traceMask, s3jni_trace_impl, ps); } +static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, + const char *zTable, sqlite3_int64 nRowid){ + PerDbStateJni * const ps = pState; + JNIEnv * const env = ps->env; + /* ACHTUNG: this will break if zDb or zTable contain chars which are + different in MUTF-8 than UTF-8. That seems like a low risk, + but it's possible. */ + jstring jDbName; + jstring jTable; + jDbName = (*env)->NewStringUTF(env, zDb); + jTable = jDbName ? (*env)->NewStringUTF(env, zTable) : 0; + IFTHREW { + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + (*env)->CallVoidMethod(env, ps->updateHook.jObj, + ps->updateHook.midCallback, + (jint)opId, jDbName, jTable, (jlong)nRowid); + IFTHREW{ + MARKER(("WARNING: callback MUST NOT THROW.\n")); + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); + } + } + UNREF_L(jDbName); + UNREF_L(jTable); +} + + +JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + JniHookState * const pHook = &ps->updateHook; + if(!ps){ + s3jni_db_error(pDb, SQLITE_NOMEM, 0); + return 0; + } + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return pOld; + } + if( !jHook ){ + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + memset(pHook, 0, sizeof(JniHookState)); + sqlite3_update_hook(pDb, 0, 0); + return pOld; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xUpdateHook", + "(ILjava/lang/String;Ljava/lang/String;J)V"); + IFTHREW { + EXCEPTION_CLEAR; + s3jni_db_error(pDb, SQLITE_ERROR, + "Cannot not find matching callback on " + "update hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + sqlite3_update_hook(pDb, s3jni_update_hook_impl, ps); + if(pOld){ + jobject tmp = REF_L(pOld); + UNREF_G(pOld); + pOld = tmp; + } + } + return pOld; +} + JDECL(jbyteArray,1value_1blob)(JENV_JSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index f9fa4a92b1..0df788eb70 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1419,6 +1419,14 @@ JNIEXPORT jlong JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1total_1changes64 JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1trace_1v2 (JNIEnv *, jclass, jobject, jint, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_update_hook + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/UpdateHook;)Lorg/sqlite/jni/UpdateHook; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1update_1hook + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_value_blob diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 36b824a808..3e9fcab17f 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -661,7 +661,7 @@ public final class SQLite3Jni { public static native int sqlite3_trace_v2(@NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer); - //TODO void *sqlite3_update_hook(sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void*); + public static native UpdateHook sqlite3_update_hook(sqlite3 db, UpdateHook hook); public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 67e3bf639a..f85e37a7b3 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -800,6 +800,56 @@ public class Tester1 { sqlite3_close_v2(db); } + private static void testUpdateHook(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder expectedOp = new ValueHolder<>(0); + final UpdateHook theHook = new UpdateHook(){ + @SuppressWarnings("unchecked") + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + ++counter.value; + if( 0!=expectedOp.value ){ + affirm( expectedOp.value == opId ); + } + } + }; + UpdateHook oldHook = sqlite3_update_hook(db, theHook); + affirm( null == oldHook ); + expectedOp.value = SQLITE_INSERT; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + affirm( 3 == counter.value ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='d' where a='c';"); + affirm( 4 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( theHook == oldHook ); + expectedOp.value = SQLITE_DELETE; + execSql(db, "DELETE FROM t where a='d'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( theHook == oldHook ); + execSql(db, "update t set a='e' where a='b';"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, null); + affirm( null == oldHook ); + + final UpdateHook newHook = new UpdateHook(){ + public void xUpdateHook(int opId, String dbName, String tableName, long rowId){ + } + }; + oldHook = sqlite3_update_hook(db, newHook); + affirm( null == oldHook ); + execSql(db, "update t set a='h' where a='a'"); + affirm( 5 == counter.value ); + oldHook = sqlite3_update_hook(db, theHook); + affirm( newHook == oldHook ); + expectedOp.value = SQLITE_UPDATE; + execSql(db, "update t set a='i' where a='h'"); + affirm( 6 == counter.value ); + sqlite3_close_v2(db); + } + + private static void testRollbackHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); @@ -863,6 +913,7 @@ public class Tester1 { testProgress(); testCommitHook(); testRollbackHook(); + testUpdateHook(); //testSleep(); if(liArgs.indexOf("-v")>0){ listBoundMethods(); diff --git a/ext/jni/src/org/sqlite/jni/UpdateHook.java b/ext/jni/src/org/sqlite/jni/UpdateHook.java new file mode 100644 index 0000000000..171e2bdb41 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/UpdateHook.java @@ -0,0 +1,25 @@ +/* +** 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_update_hook(). +*/ +public interface UpdateHook { + /** + Works as documented for the sqlite3_update_hook() callback. + Must not throw. + */ + void xUpdateHook(int opId, String dbName, String tableName, long rowId); +} diff --git a/manifest b/manifest index d9877eabbf..3950de8156 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_rollback_hook()\sto\sJNI. -D 2023-07-30T06:44:21.370 +C Bind\ssqlite3_update_hook()\sto\sJNI. +D 2023-07-30T07:44:03.881 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,9 +231,9 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d -F ext/jni/README.md b62f1f0e67a6295e9a0283d4dffad6ed30ec50352aa36b3bd323a26593606c0f -F ext/jni/src/c/sqlite3-jni.c 84c082bd07d130b3db4121c32429a9ea1e57c109366b78909b3a7bba4b9afd68 -F ext/jni/src/c/sqlite3-jni.h 58f1f1a363184348c56dd6af47a2311640dde61ab0d9f4ba51fadf6ae5ba88c3 +F ext/jni/README.md ffbf87660efb7428d2b8aa644da1ddb4a3f4ac414936a9a44ce34a3899e12520 +F ext/jni/src/c/sqlite3-jni.c 77466a0b09141349c8e751edda689592865d28e4641ca3b446ab0ac456ae091e +F ext/jni/src/c/sqlite3-jni.h 85345dd3c970b539f1de4e6ad59c245fa6e80ca775a498ab1ed3d67f8615ce34 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a @@ -242,9 +242,10 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 08a752b58a33696c5eaf0eb9361a0966 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1e0ec8a4a5f95b0f595666f9b92246f3394f31f78f62503ed1d1ab879a695101 -F ext/jni/src/org/sqlite/jni/Tester1.java 7d65095d4d4e683f937de57f9340e7eda966a9bd0adba945b86b76570a12c3a7 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 50577e5d727ca3f53b2991988a2a0203b9ead199af60814b89f34edf98aa8d5a +F ext/jni/src/org/sqlite/jni/Tester1.java 2d43b851db4189e54527e7fb4d50493c8efaa6c0781d0f5cc7f249c95b48ce3b F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 +F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d 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 841ac0384ec23e7d24ad9a928f8728b98bd3c4c3814d401200c6531786b9c241 @@ -2069,8 +2070,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P be4459b495cc2555e4d8ca24dd56c3da1036d25af7cc077bbc3d93c2a3e8c40b -R 6da75ca1ede9a6d1b13c3955b22a3d72 +P 5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee +R a218970a738fde3116e0f53364fc20ff U stephan -Z 04a18495036c22c78ae28b1e24549558 +Z eb57a2fe6bbc5fb050f3fa2299b80262 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5d2b1e0838..2872ca44b1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee \ No newline at end of file +a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde \ No newline at end of file From 70576de5e05d8bbf92f403ab82909075c27bc2a8 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 08:12:15 +0000 Subject: [PATCH 021/148] Consolidate some warnings for java callbacks which must not throw but do. FossilOrigin-Name: 5e592ed2dfc89225fff3a1c76509adc799a238282413984e0c4b32af18525d18 --- ext/jni/src/c/sqlite3-jni.c | 10 +++++----- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 85d010b304..21c768e5d6 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -153,6 +153,9 @@ #define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) #define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) +#define EXCEPTION_WARN_CALLBACK_THREW \ + MARKER(("WARNING: this routine MUST NOT THROW.\n")); \ + (*env)->ExceptionDescribe(env) #define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT #define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR @@ -897,8 +900,7 @@ static void collation_xDestroy_proxy(void *pArg){ //MARKER(("Calling Collation.xDestroy()...\n")); (*env)->CallVoidMethod(env, cs->oCollation, method); IFTHREW { - MARKER(("Collation.xDestroy() threw. Ignoring!\n")); - EXCEPTION_REPORT; + EXCEPTION_WARN_CALLBACK_THREW; EXCEPTION_CLEAR; } //MARKER(("Returned from Collation.xDestroy().\n")); @@ -1563,7 +1565,6 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, isCommit ? "xCommitHook" : "xRollbackHook", isCommit ? "()I" : "()V"); IFTHREW { - MARKER(("WARNING: callback MUST NOT THROW.\n")); EXCEPTION_REPORT; EXCEPTION_CLEAR; s3jni_db_error(pDb, SQLITE_ERROR, @@ -2119,8 +2120,7 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, ps->updateHook.midCallback, (jint)opId, jDbName, jTable, (jlong)nRowid); IFTHREW{ - MARKER(("WARNING: callback MUST NOT THROW.\n")); - EXCEPTION_REPORT; + EXCEPTION_WARN_CALLBACK_THREW; EXCEPTION_CLEAR; s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); } diff --git a/manifest b/manifest index 3950de8156..b22827fcbc 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_update_hook()\sto\sJNI. -D 2023-07-30T07:44:03.881 +C Consolidate\ssome\swarnings\sfor\sjava\scallbacks\swhich\smust\snot\sthrow\sbut\sdo. +D 2023-07-30T08:12:15.621 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md ffbf87660efb7428d2b8aa644da1ddb4a3f4ac414936a9a44ce34a3899e12520 -F ext/jni/src/c/sqlite3-jni.c 77466a0b09141349c8e751edda689592865d28e4641ca3b446ab0ac456ae091e +F ext/jni/src/c/sqlite3-jni.c 22ea3fef5e6376e4dcc94690f89034ebcb60a743608f56a57db5e561d9c166ac F ext/jni/src/c/sqlite3-jni.h 85345dd3c970b539f1de4e6ad59c245fa6e80ca775a498ab1ed3d67f8615ce34 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2070,8 +2070,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5f8ee44098d74ac2b98e4dd43ad80d3b919528358b3f992b425af7fa6262dcee -R a218970a738fde3116e0f53364fc20ff +P a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde +R 69bedfdee9cbb57a819c3589052e05da U stephan -Z eb57a2fe6bbc5fb050f3fa2299b80262 +Z feadd29e0fce2b9e30dbc845dba21259 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2872ca44b1..8880ba10c1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde \ No newline at end of file +5e592ed2dfc89225fff3a1c76509adc799a238282413984e0c4b32af18525d18 \ No newline at end of file From 9c113744d151b38a9b8fabbb119233ed0555ad41 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 09:45:54 +0000 Subject: [PATCH 022/148] Incremental checkin to minimize the diff while narrowing in on an assertion caused by refactoring. FossilOrigin-Name: 2d7a91b1396d87852f1153ab7af7385514a9537cb64ba3bbd0faba2d28704214 --- ext/jni/README.md | 6 +- ext/jni/src/c/sqlite3-jni.c | 224 +++++++++--------- .../src/org/sqlite/jni/CollationNeeded.java | 32 +++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 7 - manifest | 17 +- manifest.uuid | 2 +- 6 files changed, 158 insertions(+), 130 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/CollationNeeded.java diff --git a/ext/jni/README.md b/ext/jni/README.md index d62eddf6a7..b77f79a3f3 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -93,9 +93,9 @@ conversion in Java, and there is no JNI C API for that conversion Known consequences and limitations of this discrepancy include: -- Database and table names must not contain characters which differ - in MUTF-8 and UTF-8, or certain APIs will mis-translate them on - their way between languages. +- Names of databases, tables, and collations must not contain + characters which differ in MUTF-8 and UTF-8, or certain APIs will + mis-translate them on their way between languages. [modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 21c768e5d6..66384ef673 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -366,6 +366,7 @@ typedef struct PerDbStateJni PerDbStateJni; struct PerDbStateJni { JNIEnv *env; sqlite3 * pDb; + jobject jDb /* the object which was passed to sqlite3_open(_v2)() */; PerDbStateJni * pNext; PerDbStateJni * pPrev; JniHookState trace; @@ -484,117 +485,6 @@ static int BusyHandlerJni_init(JNIEnv * const env, BusyHandlerJni * const s, return 0; } -/** - Extracts the new PerDbStateJni instance from the free-list, or - allocates one if needed, associats it with pDb, and returns. - Returns NULL on OOM. -*/ -static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb){ - PerDbStateJni * rv; - assert( pDb ); - if(S3Global.perDb.aFree){ - rv = S3Global.perDb.aFree; - S3Global.perDb.aFree = rv->pNext; - if(rv->pNext){ - assert(rv->pNext->pPrev == rv); - assert(rv->pNext == rv->pNext->pPrev); - rv->pNext->pPrev = 0; - rv->pNext = 0; - } - }else{ - rv = s3jni_malloc(env, sizeof(PerDbStateJni)); - if(rv){ - memset(rv, 0, sizeof(PerDbStateJni)); - } - } - if(rv){ - rv->pNext = S3Global.perDb.aUsed; - S3Global.perDb.aUsed = rv; - if(rv->pNext){ - assert(!rv->pNext->pPrev); - rv->pNext->pPrev = rv; - } - rv->pDb = pDb; - rv->env = env; - } - return rv; -} - -/** - Clears s's state and moves it to the free-list. -*/ -FIXME_THREADING -static void PerDbStateJni_set_aside(PerDbStateJni * const s){ - if(s){ - JNIEnv * const env = s->env; - assert(s->pDb && "Else this object is already in the free-list."); - if(s->pNext) s->pNext->pPrev = s->pPrev; - if(s->pPrev) s->pPrev->pNext = s->pNext; - else if(S3Global.perDb.aUsed == s){ - assert(!s->pPrev); - S3Global.perDb.aUsed = s->pNext; - } - UNREF_G(s->trace.jObj); - UNREF_G(s->progress.jObj); - UNREF_G(s->commitHook.jObj); - UNREF_G(s->rollbackHook.jObj); - BusyHandlerJni_clear(&s->busyHandler); - memset(s, 0, sizeof(PerDbStateJni)); - s->pNext = S3Global.perDb.aFree; - S3Global.perDb.aFree = s; - } -} - -static void PerDbStateJni_dump(PerDbStateJni *s){ - MARKER(("PerDbStateJni->env @ %p\n", s->env)); - MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb)); - MARKER(("PerDbStateJni->trace.jObj @ %p\n", s->trace.jObj)); - MARKER(("PerDbStateJni->progress.jObj @ %p\n", s->progress.jObj)); - MARKER(("PerDbStateJni->commitHook.jObj @ %p\n", s->commitHook.jObj)); - MARKER(("PerDbStateJni->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("PerDbStateJni->busyHandler.env @ %p\n", s->busyHandler.env)); - MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); - MARKER(("PerDbStateJni->env @ %p\n", s->env)); -} - -/** - Returns the PerDbStateJni object for the given db. If allocIfNeeded is - true then a new instance will be allocated if no mapping currently - exists, else NULL is returned if no mapping is found. - -*/ -FIXME_THREADING -static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeeded){ - PerDbStateJni * s = S3Global.perDb.aUsed; - for( ; s; s = s->pNext){ - if(s->pDb == pDb) return s; - } - if(allocIfNeeded) s = PerDbStateJni_alloc(env, pDb); - return s; -} - -/** - Cleans up and frees all state in S3Global.perDb. -*/ -FIXME_THREADING -static void PerDbStateJni_free_all(void){ - PerDbStateJni * pS = S3Global.perDb.aUsed; - PerDbStateJni * pSNext = 0; - for( ; pS; pS = pSNext ){ - pSNext = pS->pNext; - PerDbStateJni_set_aside(pS); - assert(pSNext ? !pSNext->pPrev : 1); - } - assert( 0==S3Global.perDb.aUsed ); - pS = S3Global.perDb.aFree; - S3Global.perDb.aFree = 0; - pSNext = 0; - for( ; pS; pS = pSNext ){ - pSNext = pS->pNext; - s3jni_free(pSNext); - } -} - /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized @@ -751,6 +641,118 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam } } +/** + Extracts the new PerDbStateJni instance from the free-list, or + allocates one if needed, associats it with pDb, and returns. + Returns NULL on OOM. +*/ +static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb){ + PerDbStateJni * rv; + assert( pDb ); + if(S3Global.perDb.aFree){ + rv = S3Global.perDb.aFree; + S3Global.perDb.aFree = rv->pNext; + if(rv->pNext){ + assert(rv->pNext->pPrev == rv); + assert(rv->pNext == rv->pNext->pPrev); + rv->pNext->pPrev = 0; + rv->pNext = 0; + } + }else{ + rv = s3jni_malloc(env, sizeof(PerDbStateJni)); + if(rv){ + memset(rv, 0, sizeof(PerDbStateJni)); + } + } + if(rv){ + rv->pNext = S3Global.perDb.aUsed; + S3Global.perDb.aUsed = rv; + if(rv->pNext){ + assert(!rv->pNext->pPrev); + rv->pNext->pPrev = rv; + } + rv->pDb = pDb; + rv->env = env; + } + return rv; +} + +/** + Clears s's state and moves it to the free-list. +*/ +FIXME_THREADING +static void PerDbStateJni_set_aside(PerDbStateJni * const s){ + if(s){ + JNIEnv * const env = s->env; + assert(s->pDb && "Else this object is already in the free-list."); + if(s->pNext) s->pNext->pPrev = s->pPrev; + if(s->pPrev) s->pPrev->pNext = s->pNext; + else if(S3Global.perDb.aUsed == s){ + assert(!s->pPrev); + S3Global.perDb.aUsed = s->pNext; + } + UNREF_G(s->trace.jObj); + UNREF_G(s->progress.jObj); + UNREF_G(s->commitHook.jObj); + UNREF_G(s->rollbackHook.jObj); + UNREF_G(s->jDb); + BusyHandlerJni_clear(&s->busyHandler); + memset(s, 0, sizeof(PerDbStateJni)); + s->pNext = S3Global.perDb.aFree; + S3Global.perDb.aFree = s; + } +} + +static void PerDbStateJni_dump(PerDbStateJni *s){ + MARKER(("PerDbStateJni->env @ %p\n", s->env)); + MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb)); + MARKER(("PerDbStateJni->trace.jObj @ %p\n", s->trace.jObj)); + MARKER(("PerDbStateJni->progress.jObj @ %p\n", s->progress.jObj)); + MARKER(("PerDbStateJni->commitHook.jObj @ %p\n", s->commitHook.jObj)); + MARKER(("PerDbStateJni->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); + MARKER(("PerDbStateJni->busyHandler.env @ %p\n", s->busyHandler.env)); + MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); + MARKER(("PerDbStateJni->env @ %p\n", s->env)); +} + +/** + Returns the PerDbStateJni object for the given db. If allocIfNeeded is + true then a new instance will be allocated if no mapping currently + exists, else NULL is returned if no mapping is found. + +*/ +FIXME_THREADING +static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeeded){ + PerDbStateJni * s = S3Global.perDb.aUsed; + for( ; s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + if(allocIfNeeded) s = PerDbStateJni_alloc(env, pDb); + return s; +} + +/** + Cleans up and frees all state in S3Global.perDb. +*/ +FIXME_THREADING +static void PerDbStateJni_free_all(void){ + PerDbStateJni * pS = S3Global.perDb.aUsed; + PerDbStateJni * pSNext = 0; + for( ; pS; pS = pSNext ){ + pSNext = pS->pNext; + PerDbStateJni_set_aside(pS); + assert(pSNext ? !pSNext->pPrev : 1); + } + assert( 0==S3Global.perDb.aUsed ); + pS = S3Global.perDb.aFree; + S3Global.perDb.aFree = 0; + pSNext = 0; + for( ; pS; pS = pSNext ){ + pSNext = pS->pNext; + s3jni_free(pSNext); + } +} + /** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. This function calls sqlite3_aggregate_context() to allocate a tiny diff --git a/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/CollationNeeded.java new file mode 100644 index 0000000000..a9f64dc91d --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/CollationNeeded.java @@ -0,0 +1,32 @@ +/* +** 2023-07-30 +** +** 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_collation_needed(). +*/ +public interface CollationNeeded { + /** + Works as documented for the sqlite3_create_collation() callback. + Must not throw. + + Achtung: the first argument to this function is not guaranteed to + be the same object upon which ealier DB operations have been + performed, e.g. not the one passed to sqlite3_collation_needed(), + but it will refer to the same underlying C-level database + pointer. This quirk is a side effect of how per-db state is + managed in the JNI layer. + */ + int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 3e9fcab17f..c32128284e 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -254,13 +254,6 @@ public final class SQLite3Jni { int eTextRep, @NotNull Collation col); - //! Convenience overload which assumes SQLITE_UTF8 encoding. - public static int sqlite3_create_collation(@NotNull sqlite3 db, - @NotNull String name, - @NotNull Collation col){ - return sqlite3_create_collation(db, name, SQLITE_UTF8, col); - } - public static native int sqlite3_create_function(@NotNull sqlite3 db, @NotNull String functionName, int nArg, int eTextRep, diff --git a/manifest b/manifest index b22827fcbc..131b57da31 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Consolidate\ssome\swarnings\sfor\sjava\scallbacks\swhich\smust\snot\sthrow\sbut\sdo. -D 2023-07-30T08:12:15.621 +C Incremental\scheckin\sto\sminimize\sthe\sdiff\swhile\snarrowing\sin\son\san\sassertion\scaused\sby\srefactoring. +D 2023-07-30T09:45:54.025 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,18 +231,19 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d -F ext/jni/README.md ffbf87660efb7428d2b8aa644da1ddb4a3f4ac414936a9a44ce34a3899e12520 -F ext/jni/src/c/sqlite3-jni.c 22ea3fef5e6376e4dcc94690f89034ebcb60a743608f56a57db5e561d9c166ac +F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a +F ext/jni/src/c/sqlite3-jni.c 5b5631520dd2e3ae6890bcbbee2aad3d7b5c404e96ae826b661e1105d2d2a69d F ext/jni/src/c/sqlite3-jni.h 85345dd3c970b539f1de4e6ad59c245fa6e80ca775a498ab1ed3d67f8615ce34 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 +F ext/jni/src/org/sqlite/jni/CollationNeeded.java 15ca4e92b669c6412594120a9379459cd3e6e9e8ffba18c8698d879ce1142c91 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a 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 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 50577e5d727ca3f53b2991988a2a0203b9ead199af60814b89f34edf98aa8d5a +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e64bae3357cc16f9416926539e2aa08fbb5a35022a828a158cfdd3e310575324 F ext/jni/src/org/sqlite/jni/Tester1.java 2d43b851db4189e54527e7fb4d50493c8efaa6c0781d0f5cc7f249c95b48ce3b F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2070,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P a5bbaa9017839f8d8b92bfb44472d4c60fa3037bfae7846dc8350262c1332cde -R 69bedfdee9cbb57a819c3589052e05da +P 5e592ed2dfc89225fff3a1c76509adc799a238282413984e0c4b32af18525d18 +R a4ee8dd84c4c810bd2a0b503fc8278d5 U stephan -Z feadd29e0fce2b9e30dbc845dba21259 +Z f4150e59d6c4033aef1bd32347c40f57 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8880ba10c1..be036f7c53 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5e592ed2dfc89225fff3a1c76509adc799a238282413984e0c4b32af18525d18 \ No newline at end of file +2d7a91b1396d87852f1153ab7af7385514a9537cb64ba3bbd0faba2d28704214 \ No newline at end of file From 45fe10d02b067af9fb26d3215419f00aa9e24444 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 10:47:38 +0000 Subject: [PATCH 023/148] Internal JNI refacoring to support the pending sqlite3_collation_needed() callback. Correct a bug in the linked-list handling of PerDbStateJni which triggered an assert(). FossilOrigin-Name: 7ac6614e69b03304d09745619ed83f12c7eb775aaf4a636a79289b01642ddd14 --- ext/jni/src/c/sqlite3-jni.c | 179 +++++++++++++++++++++--------------- manifest | 12 +-- manifest.uuid | 2 +- 3 files changed, 113 insertions(+), 80 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 66384ef673..1ad16daac0 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -366,7 +366,8 @@ typedef struct PerDbStateJni PerDbStateJni; struct PerDbStateJni { JNIEnv *env; sqlite3 * pDb; - jobject jDb /* the object which was passed to sqlite3_open(_v2)() */; + jobject jDb /* a global ref of the object which was passed to + sqlite3_open(_v2)() */; PerDbStateJni * pNext; PerDbStateJni * pPrev; JniHookState trace; @@ -644,22 +645,29 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam /** Extracts the new PerDbStateJni instance from the free-list, or allocates one if needed, associats it with pDb, and returns. - Returns NULL on OOM. + Returns NULL on OOM. pDb MUST be associated with jDb via + setNativePointer(). */ -static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb){ +static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb, jobject jDb){ PerDbStateJni * rv; assert( pDb ); if(S3Global.perDb.aFree){ rv = S3Global.perDb.aFree; + //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); + //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext)); S3Global.perDb.aFree = rv->pNext; + assert(rv->pNext != rv); + assert(rv->pPrev != rv); + assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1); if(rv->pNext){ assert(rv->pNext->pPrev == rv); - assert(rv->pNext == rv->pNext->pPrev); + assert(rv->pPrev ? (rv->pNext == rv->pPrev->pNext) : 1); rv->pNext->pPrev = 0; rv->pNext = 0; } }else{ rv = s3jni_malloc(env, sizeof(PerDbStateJni)); + //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); if(rv){ memset(rv, 0, sizeof(PerDbStateJni)); } @@ -671,6 +679,7 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb){ assert(!rv->pNext->pPrev); rv->pNext->pPrev = rv; } + rv->jDb = REF_G(jDb); rv->pDb = pDb; rv->env = env; } @@ -685,6 +694,10 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ if(s){ JNIEnv * const env = s->env; assert(s->pDb && "Else this object is already in the free-list."); + //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); + assert(s->pPrev != s); + assert(s->pNext != s); + assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); if(s->pNext) s->pNext->pPrev = s->pPrev; if(s->pPrev) s->pPrev->pNext = s->pNext; else if(S3Global.perDb.aUsed == s){ @@ -699,7 +712,10 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ BusyHandlerJni_clear(&s->busyHandler); memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; + if(s->pNext) s->pNext->pPrev = s; S3Global.perDb.aFree = s; + //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); + //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); } } @@ -720,14 +736,24 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ true then a new instance will be allocated if no mapping currently exists, else NULL is returned if no mapping is found. + The 3rd and 4th args should only be non-0 for + sqlite3_open(_v2)(). For all other cases, they must be 0, in which + case the db handle will be fished out of the jDb object and NULL is + returned if jDb does not have any associated PerDbStateJni. */ FIXME_THREADING -static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, sqlite3 *pDb, int allocIfNeeded){ +static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ PerDbStateJni * s = S3Global.perDb.aUsed; - for( ; s; s = s->pNext){ + assert(allocIfNeeded ? !!pDb : 1); + if(!allocIfNeeded && !pDb){ + pDb = PtrGet_sqlite3_value(jDb); + } + for( ; pDb && s; s = s->pNext){ if(s->pDb == pDb) return s; } - if(allocIfNeeded) s = PerDbStateJni_alloc(env, pDb); + if(allocIfNeeded){ + s = PerDbStateJni_alloc(env, pDb, jDb); + } return s; } @@ -736,19 +762,19 @@ static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, sqlite3 *pDb, int alloc */ FIXME_THREADING static void PerDbStateJni_free_all(void){ - PerDbStateJni * pS = S3Global.perDb.aUsed; + PerDbStateJni * ps = S3Global.perDb.aUsed; PerDbStateJni * pSNext = 0; - for( ; pS; pS = pSNext ){ - pSNext = pS->pNext; - PerDbStateJni_set_aside(pS); + for( ; ps; ps = pSNext ){ + pSNext = ps->pNext; + PerDbStateJni_set_aside(ps); assert(pSNext ? !pSNext->pPrev : 1); } assert( 0==S3Global.perDb.aUsed ); - pS = S3Global.perDb.aFree; + ps = S3Global.perDb.aFree; S3Global.perDb.aFree = 0; pSNext = 0; - for( ; pS; pS = pSNext ){ - pSNext = pS->pNext; + for( ; ps; ps = pSNext ){ + pSNext = ps->pNext; s3jni_free(pSNext); } } @@ -1358,59 +1384,59 @@ JDECL(jint,1bind_1zeroblob64)(JENV_JSELF, jobject jpStmt, } static int s3jni_busy_handler(void* pState, int n){ - PerDbStateJni * const pS = (PerDbStateJni *)pState; + PerDbStateJni * const ps = (PerDbStateJni *)pState; int rc = 0; - if( pS->busyHandler.jObj ){ - JNIEnv * const env = pS->env; - rc = (*env)->CallIntMethod(env, pS->busyHandler.jObj, - pS->busyHandler.jmidxCallback, (jint)n); + if( ps->busyHandler.jObj ){ + JNIEnv * const env = ps->env; + rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, + ps->busyHandler.jmidxCallback, (jint)n); IFTHREW_CLEAR; } return rc; } JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ - sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * const pS = PerDbStateJni_for_db(env, pDb, 1); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); int rc; - if(!pS) return (jint)SQLITE_NOMEM; + if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ - if(pS->busyHandler.jObj && - (*env)->IsSameObject(env, pS->busyHandler.jObj, jBusy)){ + if(ps->busyHandler.jObj && + (*env)->IsSameObject(env, ps->busyHandler.jObj, jBusy)){ /* Same object - this is a no-op. */ return 0; } - rc = BusyHandlerJni_init(env, &pS->busyHandler, jBusy); + rc = BusyHandlerJni_init(env, &ps->busyHandler, jBusy); if(rc){ - assert(!pS->busyHandler.jObj); + assert(!ps->busyHandler.jObj); return (jint)rc; } - assert(pS->busyHandler.jObj && pS->busyHandler.klazz); - assert( (*env)->IsSameObject(env, pS->busyHandler.jObj, jBusy) ); + assert(ps->busyHandler.jObj && ps->busyHandler.klazz); + assert( (*env)->IsSameObject(env, ps->busyHandler.jObj, jBusy) ); }else{ - BusyHandlerJni_clear(&pS->busyHandler); + BusyHandlerJni_clear(&ps->busyHandler); } return jBusy - ? sqlite3_busy_handler(pDb, s3jni_busy_handler, pS) - : sqlite3_busy_handler(pDb, 0, 0); + ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) + : sqlite3_busy_handler(ps->pDb, 0, 0); } JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ - sqlite3* const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * const pS = PerDbStateJni_for_db(env, pDb, 0); - if( pS && pS->busyHandler.jObj ){ - BusyHandlerJni_clear(&pS->busyHandler); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + if( ps ){ + if( ps->busyHandler.jObj ){ + BusyHandlerJni_clear(&ps->busyHandler); + } + return sqlite3_busy_timeout(ps->pDb, (int)ms); } - return sqlite3_busy_timeout(pDb, (int)ms); + return SQLITE_MISUSE; } /** Wrapper for sqlite3_close(_v2)(). */ static jint s3jni_close_db(JNIEnv *env, jobject jDb, int version){ - sqlite3 * pDb; int rc = 0; - PerDbStateJni * pS = 0; + PerDbStateJni * ps = 0; assert(version == 1 || version == 2); if(0){ PerDbStateJni * s = S3Global.perDb.aUsed; @@ -1418,12 +1444,11 @@ static jint s3jni_close_db(JNIEnv *env, jobject jDb, int version){ PerDbStateJni_dump(s); } } - pDb = PtrGet_sqlite3(jDb); - if(!pDb) return rc; - pS = PerDbStateJni_for_db(env, pDb, 0); - rc = 1==version ? (jint)sqlite3_close(pDb) : (jint)sqlite3_close_v2(pDb); - if(pS) PerDbStateJni_set_aside(pS) - /* MUST come after close() because of pS->trace. */; + ps = PerDbStateJni_for_db(env, jDb, 0, 0); + if(!ps) return rc; + rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); + if(ps) PerDbStateJni_set_aside(ps) + /* MUST come after close() because of ps->trace. */; setNativePointer(env, jDb, 0, ClassNames.sqlite3); return (jint)rc; } @@ -1536,14 +1561,13 @@ static void s3jni_rollback_hook_impl(void *pP){ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, jobject jHook){ - sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; JniHookState * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; if(!ps){ - s3jni_db_error(pDb, SQLITE_NOMEM, 0); + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; } pOld = pHook->jObj; @@ -1558,8 +1582,8 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, pOld = tmp; } memset(pHook, 0, sizeof(JniHookState)); - if( isCommit ) sqlite3_commit_hook(pDb, 0, 0); - else sqlite3_rollback_hook(pDb, 0, 0); + if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); + else sqlite3_rollback_hook(ps->pDb, 0, 0); return pOld; } klazz = (*env)->GetObjectClass(env, jHook); @@ -1569,14 +1593,14 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, IFTHREW { EXCEPTION_REPORT; EXCEPTION_CLEAR; - s3jni_db_error(pDb, SQLITE_ERROR, + s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching callback on " "hook object."); }else{ pHook->midCallback = xCallback; pHook->jObj = REF_G(jHook); - if( isCommit ) sqlite3_commit_hook(pDb, s3jni_commit_hook_impl, ps); - else sqlite3_rollback_hook(pDb, s3jni_rollback_hook_impl, ps); + if( isCommit ) sqlite3_commit_hook(ps->pDb, s3jni_commit_hook_impl, ps); + else sqlite3_rollback_hook(ps->pDb, s3jni_rollback_hook_impl, ps); if(pOld){ jobject tmp = REF_L(pOld); UNREF_G(pOld); @@ -1729,27 +1753,39 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_JSELF, jobject jpDb){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } +static int s3jni_open_post(JNIEnv *env, sqlite3 **ppDb, jobject jDb, int theRc){ + if(1 && *ppDb){ + PerDbStateJni * const s = PerDbStateJni_for_db(env, jDb, *ppDb, 1); + if(!s && 0==theRc){ + sqlite3_close(*ppDb); + *ppDb = 0; + theRc = SQLITE_NOMEM; + } + } + setNativePointer(env, jDb, *ppDb, ClassNames.sqlite3); + return theRc; +} -JDECL(jint,1open)(JENV_JSELF, jstring strName, jobject ppOut){ +JDECL(jint,1open)(JENV_JSELF, jstring strName, jobject jOut){ sqlite3 * pOut = 0; const char *zName = strName ? JSTR_TOC(strName) : 0; int nrc = sqlite3_open(zName, &pOut); //MARKER(("env=%p, *env=%p\n", env, *env)); - setNativePointer(env, ppOut, pOut, ClassNames.sqlite3); + nrc = s3jni_open_post(env, &pOut, jOut, nrc); assert(nrc==0 ? pOut!=0 : 1); JSTR_RELEASE(strName, zName); return (jint)nrc; } JDECL(jint,1open_1v2)(JENV_JSELF, jstring strName, - jobject ppOut, jint flags, jstring strVfs){ + jobject jOut, jint flags, jstring strVfs){ sqlite3 * pOut = 0; const char *zName = strName ? JSTR_TOC(strName) : 0; const char *zVfs = strVfs ? JSTR_TOC(strVfs) : 0; int nrc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", zName, zVfs, pOut, (int)flags, nrc));*/ - setNativePointer(env, ppOut, pOut, ClassNames.sqlite3); + nrc = s3jni_open_post(env, &pOut, jOut, nrc); assert(nrc==0 ? pOut!=0 : 1); JSTR_RELEASE(strName, zName); JSTR_RELEASE(strVfs, zVfs); @@ -1820,8 +1856,7 @@ static int s3jni_progress_handler_impl(void *pP){ } JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress){ - sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * ps = PerDbStateJni_for_db(env, pDb, 1); + PerDbStateJni * ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; jmethodID xCallback; if( n<1 || !jProgress ){ @@ -1829,25 +1864,25 @@ JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress UNREF_G(ps->progress.jObj); memset(&ps->progress, 0, sizeof(ps->progress)); } - sqlite3_progress_handler(pDb, 0, 0, 0); + sqlite3_progress_handler(ps->pDb, 0, 0, 0); return; } if(!ps){ - s3jni_db_error(pDb, SQLITE_NOMEM, 0); + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return; } klazz = (*env)->GetObjectClass(env, jProgress); xCallback = (*env)->GetMethodID(env, klazz, "xCallback", "()I"); IFTHREW { EXCEPTION_CLEAR; - s3jni_db_error(pDb, SQLITE_ERROR, + s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching xCallback() on " "ProgressHandler object."); }else{ UNREF_G(ps->progress.jObj); ps->progress.midCallback = xCallback; ps->progress.jObj = REF_G(jProgress); - sqlite3_progress_handler(pDb, (int)n, s3jni_progress_handler_impl, ps); + sqlite3_progress_handler(ps->pDb, (int)n, s3jni_progress_handler_impl, ps); } } @@ -2081,15 +2116,14 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ } JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ - sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; if( !traceMask || !jTracer ){ if(ps){ UNREF_G(ps->trace.jObj); memset(&ps->trace, 0, sizeof(ps->trace)); } - return (jint)sqlite3_trace_v2(pDb, 0, 0, 0); + return (jint)sqlite3_trace_v2(ps->pDb, 0, 0, 0); } if(!ps) return SQLITE_NOMEM; klazz = (*env)->GetObjectClass(env, jTracer); @@ -2097,11 +2131,11 @@ JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ "(IJLjava/lang/Object;)I"); IFTHREW { EXCEPTION_CLEAR; - return s3jni_db_error(pDb, SQLITE_ERROR, + return s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching xCallback() on Tracer object."); } ps->trace.jObj = REF_G(jTracer); - return sqlite3_trace_v2(pDb, (unsigned)traceMask, s3jni_trace_impl, ps); + return sqlite3_trace_v2(ps->pDb, (unsigned)traceMask, s3jni_trace_impl, ps); } static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, @@ -2133,14 +2167,13 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ - sqlite3 * const pDb = PtrGet_sqlite3(jDb); - PerDbStateJni * const ps = PerDbStateJni_for_db(env, pDb, 1); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; JniHookState * const pHook = &ps->updateHook; if(!ps){ - s3jni_db_error(pDb, SQLITE_NOMEM, 0); + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; } pOld = pHook->jObj; @@ -2155,7 +2188,7 @@ JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ pOld = tmp; } memset(pHook, 0, sizeof(JniHookState)); - sqlite3_update_hook(pDb, 0, 0); + sqlite3_update_hook(ps->pDb, 0, 0); return pOld; } klazz = (*env)->GetObjectClass(env, jHook); @@ -2163,13 +2196,13 @@ JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ "(ILjava/lang/String;Ljava/lang/String;J)V"); IFTHREW { EXCEPTION_CLEAR; - s3jni_db_error(pDb, SQLITE_ERROR, + s3jni_db_error(ps->pDb, SQLITE_ERROR, "Cannot not find matching callback on " "update hook object."); }else{ pHook->midCallback = xCallback; pHook->jObj = REF_G(jHook); - sqlite3_update_hook(pDb, s3jni_update_hook_impl, ps); + sqlite3_update_hook(ps->pDb, s3jni_update_hook_impl, ps); if(pOld){ jobject tmp = REF_L(pOld); UNREF_G(pOld); diff --git a/manifest b/manifest index 131b57da31..4542011eac 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Incremental\scheckin\sto\sminimize\sthe\sdiff\swhile\snarrowing\sin\son\san\sassertion\scaused\sby\srefactoring. -D 2023-07-30T09:45:54.025 +C Internal\sJNI\srefacoring\sto\ssupport\sthe\spending\ssqlite3_collation_needed()\scallback.\sCorrect\sa\sbug\sin\sthe\slinked-list\shandling\sof\sPerDbStateJni\swhich\striggered\san\sassert(). +D 2023-07-30T10:47:38.755 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 5b5631520dd2e3ae6890bcbbee2aad3d7b5c404e96ae826b661e1105d2d2a69d +F ext/jni/src/c/sqlite3-jni.c 57db39bd2443435764777a1e43e2f8e356b8c411ee2649ad08df4b32087cbe80 F ext/jni/src/c/sqlite3-jni.h 85345dd3c970b539f1de4e6ad59c245fa6e80ca775a498ab1ed3d67f8615ce34 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5e592ed2dfc89225fff3a1c76509adc799a238282413984e0c4b32af18525d18 -R a4ee8dd84c4c810bd2a0b503fc8278d5 +P 2d7a91b1396d87852f1153ab7af7385514a9537cb64ba3bbd0faba2d28704214 +R 5bff367529a1829789141e745a6b193a U stephan -Z f4150e59d6c4033aef1bd32347c40f57 +Z a4b019fdb819701e83942a71127183e7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index be036f7c53..b867e2523a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2d7a91b1396d87852f1153ab7af7385514a9537cb64ba3bbd0faba2d28704214 \ No newline at end of file +7ac6614e69b03304d09745619ed83f12c7eb775aaf4a636a79289b01642ddd14 \ No newline at end of file From 502a5c2e26e2a0a2326538a0a491aee00d51b662 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 11:36:41 +0000 Subject: [PATCH 024/148] Bind sqlite3_collation_needed() to JNI. Related adjacent cleanups and fixes. FossilOrigin-Name: 16ff167691733350907d2d995c774a885214acd0fe8ec491c16b786f00fe85d4 --- ext/jni/src/c/sqlite3-jni.c | 86 ++++++++++++++++++- ext/jni/src/c/sqlite3-jni.h | 8 ++ .../src/org/sqlite/jni/CollationNeeded.java | 14 ++- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 18 +++- ext/jni/src/org/sqlite/jni/Tester1.java | 19 +++- ext/jni/src/org/sqlite/jni/sqlite3.java | 9 -- ext/jni/src/org/sqlite/jni/sqlite3_stmt.java | 9 -- manifest | 24 +++--- manifest.uuid | 2 +- 9 files changed, 142 insertions(+), 47 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 1ad16daac0..1f7c5bb003 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -367,7 +367,13 @@ struct PerDbStateJni { JNIEnv *env; sqlite3 * pDb; jobject jDb /* a global ref of the object which was passed to - sqlite3_open(_v2)() */; + sqlite3_open(_v2)(). We need this in order to have an + object to pass to sqlite3_collation_needed()'s + callback, or else we have to dynamically create one + for that purpose, which would be fine except that it + would be a different instance (and maybe even a + different class) than the one the user expects to + receive. */; PerDbStateJni * pNext; PerDbStateJni * pPrev; JniHookState trace; @@ -375,6 +381,7 @@ struct PerDbStateJni { JniHookState commitHook; JniHookState rollbackHook; JniHookState updateHook; + JniHookState collationNeeded; BusyHandlerJni busyHandler; }; @@ -744,6 +751,7 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ FIXME_THREADING static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ PerDbStateJni * s = S3Global.perDb.aUsed; + if(!jDb) return 0; assert(allocIfNeeded ? !!pDb : 1); if(!allocIfNeeded && !pDb){ pDb = PtrGet_sqlite3_value(jDb); @@ -1461,6 +1469,75 @@ JDECL(jint,1close)(JENV_JSELF, jobject pDb){ return s3jni_close_db(env, pDb, 1); } +/** + Assumes z is an array of unsigned short and returns the index in + that array of the first element with the value 0. +*/ +static unsigned int s3jni_utf16_strlen(void const * z){ + unsigned int i = 0; + const unsigned short * p = z; + while( p[i] ) ++i; + return i; +} + +static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, + int eTextRep, const void * z16Name){ + PerDbStateJni * const ps = pState; + JNIEnv * const env = ps->env; + unsigned int const nName = s3jni_utf16_strlen(z16Name); + jstring jName; + jName = (*env)->NewString(env, (jchar const *)z16Name, nName); + IFTHREW { + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + }else{ + (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, + ps->collationNeeded.midCallback, + ps->jDb, (jint)eTextRep, jName); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_CLEAR; + s3jni_db_error(ps->pDb, SQLITE_ERROR, "collation-needed hook threw."); + } + } + UNREF_L(jName); +} + +JDECL(jint,1collation_1needed)(JENV_JSELF, jobject jDb, jobject jHook){ + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + jclass klazz; + jobject pOld = 0; + jmethodID xCallback; + JniHookState * const pHook = &ps->collationNeeded; + int rc; + if(!ps) return SQLITE_MISUSE; + pOld = pHook->jObj; + if(pOld && jHook && + (*env)->IsSameObject(env, pOld, jHook)){ + return 0; + } + if( !jHook ){ + UNREF_G(pOld); + memset(pHook, 0, sizeof(JniHookState)); + sqlite3_collation_needed(ps->pDb, 0, 0); + return 0; + } + klazz = (*env)->GetObjectClass(env, jHook); + xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", + "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); + IFTHREW { + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_MISUSE, + "Cannot not find matching callback on " + "collation-needed hook object."); + }else{ + pHook->midCallback = xCallback; + pHook->jObj = REF_G(jHook); + UNREF_G(pOld); + rc = sqlite3_collation_needed16(ps->pDb, ps, s3jni_collation_needed_impl16); + } + return rc; +} + JDECL(jbyteArray,1column_1blob)(JENV_JSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); @@ -1753,6 +1830,11 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_JSELF, jobject jpDb){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } +/** + Code common to both the sqlite3_open() and sqlite3_open_v2() + bindings. Allocates the PerDbStateJni for *ppDb if *ppDb is not + NULL. +*/ static int s3jni_open_post(JNIEnv *env, sqlite3 **ppDb, jobject jDb, int theRc){ if(1 && *ppDb){ PerDbStateJni * const s = PerDbStateJni_for_db(env, jDb, *ppDb, 1); @@ -2173,7 +2255,7 @@ JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ jmethodID xCallback; JniHookState * const pHook = &ps->updateHook; if(!ps){ - s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); return 0; } pOld = pHook->jObj; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 0df788eb70..07d2524be0 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1019,6 +1019,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1type JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1value (JNIEnv *, jclass, jobject, jint); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_collation_needed + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/CollationNeeded;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1collation_1needed + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_context_db_handle diff --git a/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/CollationNeeded.java index a9f64dc91d..1bd8be6290 100644 --- a/ext/jni/src/org/sqlite/jni/CollationNeeded.java +++ b/ext/jni/src/org/sqlite/jni/CollationNeeded.java @@ -18,15 +18,13 @@ package org.sqlite.jni; */ public interface CollationNeeded { /** - Works as documented for the sqlite3_create_collation() callback. - Must not throw. + Has the same semantics as the C-level sqlite3_create_collation() + callback. Must not throw. - Achtung: the first argument to this function is not guaranteed to - be the same object upon which ealier DB operations have been - performed, e.g. not the one passed to sqlite3_collation_needed(), - but it will refer to the same underlying C-level database - pointer. This quirk is a side effect of how per-db state is - managed in the JNI layer. + Pedantic note: the first argument to this function will always be + the same object reference which was passed to sqlite3_open() or + sqlite3_open_v2(), even if the client has managed to create other + Java-side references to the same C-level object. */ int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index c32128284e..bdf89a3d01 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -235,8 +235,8 @@ public final class SQLite3Jni { public static native sqlite3_value sqlite3_column_value(@NotNull sqlite3_stmt stmt, int ndx); - // TODO public static native int sqlite3_collation_needed( - //sqlite3 db, void(*)(void*,sqlite3*,int eTextRep,const char*)) + public static native int sqlite3_collation_needed(@NotNull sqlite3 db, + @Nullable CollationNeeded callback); //TODO public static native int sqlite3_collation_needed16( // sqlite3 db, void(*)(void*,sqlite3*,int eTextRep,const void*) @@ -284,6 +284,20 @@ public final class SQLite3Jni { public static native int sqlite3_libversion_number(); + /** + Works like its C counterpart and makes the native pointer of the + underling (sqlite3*) object available via + ppDb.getNativePointer(). That pointer is necessary for looking up + the JNI-side native, but clients need not pay it any + heed. Passing the object to sqlite3_close() or sqlite3_close_v2() + will clear that pointer mapping. + + Pedantic note: though any number of Java-level sqlite3 objects + may refer to/wrap a single C-level (sqlite3*), the JNI internals + take a reference to the object which is passed to sqlite3_open() + or sqlite3_open_v2() so that they have a predictible object to + pass to, e.g., the sqlite3_collation_needed() callback. + */ public static native int sqlite3_open(@Nullable String filename, @NotNull sqlite3 ppDb); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index f85e37a7b3..287b61a76a 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -353,7 +353,7 @@ public class Tester1 { } private static void testCollation(){ - sqlite3 db = createNewDb(); + final sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); final ValueHolder xDestroyCalled = new ValueHolder<>(false); final Collation myCollation = new Collation() { @@ -380,10 +380,19 @@ public class Tester1 { xDestroyCalled.value = true; } }; - int rc = sqlite3_create_collation(db, "reversi", SQLITE_UTF8, myCollation); - affirm(0 == rc); + final CollationNeeded collLoader = new CollationNeeded(){ + public int xCollationNeeded(sqlite3 dbArg, int eTextRep, String collationName){ + affirm(dbArg == db/* as opposed to a temporary object*/); + return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation); + } + }; + int rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc ); + rc = sqlite3_collation_needed(db, collLoader); + affirm( 0 == rc /* Installing the same object again is a no-op */); sqlite3_stmt stmt = new sqlite3_stmt(); - sqlite3_prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi", stmt); + rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi", stmt); + affirm( 0 == rc ); int counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ final String val = sqlite3_column_text(stmt, 0); @@ -412,6 +421,8 @@ public class Tester1 { affirm(3 == counter); sqlite3_finalize(stmt); affirm(!xDestroyCalled.value); + rc = sqlite3_collation_needed(db, null); + affirm( 0 == rc ); sqlite3_close_v2(db); affirm(xDestroyCalled.value); } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java index 82bb559117..da1c00a966 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -23,13 +23,4 @@ public class sqlite3 extends NativePointerHolder { 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); - } } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java index 199689a8bd..2b5bbd54d0 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java @@ -23,13 +23,4 @@ public class sqlite3_stmt extends NativePointerHolder { 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); - } } diff --git a/manifest b/manifest index 4542011eac..4ba95e2df1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sJNI\srefacoring\sto\ssupport\sthe\spending\ssqlite3_collation_needed()\scallback.\sCorrect\sa\sbug\sin\sthe\slinked-list\shandling\sof\sPerDbStateJni\swhich\striggered\san\sassert(). -D 2023-07-30T10:47:38.755 +C Bind\ssqlite3_collation_needed()\sto\sJNI.\sRelated\sadjacent\scleanups\sand\sfixes. +D 2023-07-30T11:36:41.439 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,25 +232,25 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 57db39bd2443435764777a1e43e2f8e356b8c411ee2649ad08df4b32087cbe80 -F ext/jni/src/c/sqlite3-jni.h 85345dd3c970b539f1de4e6ad59c245fa6e80ca775a498ab1ed3d67f8615ce34 +F ext/jni/src/c/sqlite3-jni.c 1934a72f33fe356d8af810a8a662dd8109026cd0bbf298dda1fe8bd1146603ad +F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 -F ext/jni/src/org/sqlite/jni/CollationNeeded.java 15ca4e92b669c6412594120a9379459cd3e6e9e8ffba18c8698d879ce1142c91 +F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a 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 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e64bae3357cc16f9416926539e2aa08fbb5a35022a828a158cfdd3e310575324 -F ext/jni/src/org/sqlite/jni/Tester1.java 2d43b851db4189e54527e7fb4d50493c8efaa6c0781d0f5cc7f249c95b48ce3b +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2c4564b19f5366927c9a5062e36ffb7744e7f69d00b3f8ce35fe59b2f3d60698 +F ext/jni/src/org/sqlite/jni/Tester1.java a89a87f8debd89f3488a65cb42af8e14fb0150b05d5a4a3592fb86d0cfda3287 F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d 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.java 4058fbd63eb7085b5dc2daef4130623f464efdc838aafab8b9a4808c7cb01b6b F ext/jni/src/org/sqlite/jni/sqlite3_context.java 841ac0384ec23e7d24ad9a928f8728b98bd3c4c3814d401200c6531786b9c241 -F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 3193693440071998a66870544d1d2314f144bea397ce4c3f83ff225d587067a0 +F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java f602b12521a66992299ca2877260d87bc69176b1bb05201f3b46825cb3cba315 F ext/jni/src/org/sqlite/jni/sqlite3_value.java f9d8c0766b1d1b290564cb35db8d37be54c42adc8df22ee77b8d39e3e93398cd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2d7a91b1396d87852f1153ab7af7385514a9537cb64ba3bbd0faba2d28704214 -R 5bff367529a1829789141e745a6b193a +P 7ac6614e69b03304d09745619ed83f12c7eb775aaf4a636a79289b01642ddd14 +R fe2ec7cfe7eced93fd3b168114e0d2e0 U stephan -Z a4b019fdb819701e83942a71127183e7 +Z 9246dbb52619ce19a9defcf2e690b44f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b867e2523a..dce9f4cf7d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7ac6614e69b03304d09745619ed83f12c7eb775aaf4a636a79289b01642ddd14 \ No newline at end of file +16ff167691733350907d2d995c774a885214acd0fe8ec491c16b786f00fe85d4 \ No newline at end of file From 13b059025f2eb5d714c159139c40793d89199ef2 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 13:30:52 +0000 Subject: [PATCH 025/148] Minor tweaks and optimizations in the JNI bindings. FossilOrigin-Name: 41fb5898f1a78d9fd85a020e28a6048a7359b54e35632e9072917cbdbcd8b07d --- ext/jni/src/c/sqlite3-jni.c | 78 +++++++++---------- .../org/sqlite/jni/NativePointerHolder.java | 17 ++-- ext/jni/src/org/sqlite/jni/OutputPointer.java | 2 - ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 47 +++++++++-- ext/jni/src/org/sqlite/jni/Tester1.java | 10 ++- manifest | 20 ++--- manifest.uuid | 2 +- 7 files changed, 101 insertions(+), 75 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 1f7c5bb003..8db9416394 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -285,8 +285,7 @@ struct NphCacheLine { from the ClassNames struct. */; jclass klazz /* global ref to concrete NativePointerHolder class */; jmethodID midCtor /* klazz's constructor */; - jmethodID midSet /* NativePointerHolder.setNativePointer() */; - jmethodID midGet /* NativePointerHolder.getNativePointer() */; + jfieldID fidValue /* NativePointerHolder.nativePointer and OutputPointer.X.value */; jfieldID fidSetAgg /* sqlite3_context::aggregateContext */; }; @@ -565,11 +564,12 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN for( i = 0; i < NphCache_SIZE; ++i ){ cacheLine = &envRow->nph[i]; if(zClassName == cacheLine->zClassName){ -#if 0 +#define DUMP_NPH_CACHES 0 +#if DUMP_NPH_CACHES static unsigned int n = 0; - MARKER(("Cache hit #%u %s klazz@%p getter@%p, setter@%p, ctor@%p\n", - ++n, zClassName, cacheLine->klazz, cacheLine->midGet, - cacheLine->midSet, cacheLine->midCtor)); + MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", + ++n, zClassName, cacheLine->klazz, cacheLine->fidValue, + cacheLine->midCtor)); #endif assert(cacheLine->klazz); return cacheLine; @@ -580,18 +580,27 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN if(freeSlot){ freeSlot->zClassName = zClassName; freeSlot->klazz = REF_G((*env)->FindClass(env, zClassName)); -#if 0 +#if DUMP_NPH_CACHES static unsigned int cacheMisses = 0; - MARKER(("Cache miss #%u %s klazz@%p getter@%p, setter@%p, ctor@%p\n", + MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", ++cacheMisses, zClassName, freeSlot->klazz, - freeSlot->midGet, freeSlot->midSet, freeSlot->midCtor)); + freeSlot->fidValue, freeSlot->midCtor)); #endif +#undef DUMP_NPH_CACHES }else{ (*env)->FatalError(env, "MAINTENANCE REQUIRED: NphCache_SIZE is too low."); } return freeSlot; } +static jfieldID getNativePointerField(JNIEnv *env, jclass klazz){ + jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); + IFTHREW{ + (*env)->FatalError(env, "Maintenance required: missing nativePointer field."); + } + return rv; +} + /** Sets a native ptr value in NativePointerHolder object ppOut. zClassName must be a static string so we can use its address @@ -599,24 +608,24 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN */ static void setNativePointer(JNIEnv * env, jobject ppOut, void * p, const char *zClassName){ - jmethodID setter = 0; + jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); - if(cacheLine && cacheLine->klazz && cacheLine->midSet){ + if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ assert(zClassName == cacheLine->zClassName); - setter = cacheLine->midSet; + setter = cacheLine->fidValue; assert(setter); }else{ jclass const klazz = cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, ppOut); - setter = (*env)->GetMethodID(env, klazz, "setNativePointer", "(J)V"); + setter = getNativePointerField(env, klazz); if(cacheLine){ assert(cacheLine->klazz); - assert(!cacheLine->midSet); + assert(!cacheLine->fidValue); assert(zClassName == cacheLine->zClassName); - cacheLine->midSet = setter; + cacheLine->fidValue = setter; } } - (*env)->CallVoidMethod(env, ppOut, setter, (jlong)p); + (*env)->SetLongField(env, ppOut, setter, (jlong)p); IFTHREW_REPORT; } @@ -628,22 +637,22 @@ static void setNativePointer(JNIEnv * env, jobject ppOut, void * p, static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassName){ if( 0==pObj ) return 0; else{ - jmethodID getter = 0; + jfieldID getter = 0; void * rv = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); - if(cacheLine && cacheLine->midGet){ - getter = cacheLine->midGet; + if(cacheLine && cacheLine->fidValue){ + getter = cacheLine->fidValue; }else{ jclass const klazz = cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, pObj); - getter = (*env)->GetMethodID(env, klazz, "getNativePointer", "()J"); + getter = getNativePointerField(env, klazz); if(cacheLine){ assert(cacheLine->klazz); assert(zClassName == cacheLine->zClassName); - cacheLine->midGet = getter; + cacheLine->fidValue = getter; } } - rv = (void*)(*env)->CallLongMethod(env, pObj, getter); + rv = (void*)(*env)->GetLongField(env, pObj, getter); IFTHREW_REPORT; return rv; } @@ -845,34 +854,23 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, /* Sets a native int32 value in OutputPointer.Int32 object ppOut. */ static void setOutputInt32(JNIEnv * env, jobject ppOut, int v){ - jmethodID setter = 0; + jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, ClassNames.OutputPointer_Int32); - if(cacheLine && cacheLine->klazz && cacheLine->midSet){ - setter = cacheLine->midSet; + if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ + setter = cacheLine->fidValue; }else{ const jclass klazz = (*env)->GetObjectClass(env, ppOut); - setter = (*env)->GetMethodID(env, klazz, "setValue", "(I)V"); + setter = (*env)->GetFieldID(env, klazz, "value", "I"); if(cacheLine){ - assert(!cacheLine->midSet); - cacheLine->midSet = setter; + assert(!cacheLine->fidValue); + cacheLine->fidValue = setter; } } - (*env)->CallVoidMethod(env, ppOut, setter, (jint)v); + (*env)->SetIntField(env, ppOut, setter, (jint)v); IFTHREW_REPORT; } -#if 0 -/* Fetches a native int32 value from OutputPointer.Int32 object pObj. */ -static int getOutputInt(JNIEnv * env, jobject pObj){ - const jclass klazz = (*env)->GetObjectClass(env, pObj); - const jmethodID getter = - (*env)->GetMethodID(env, klazz, "getValue", "(V)I;"); - return (int)(*env)->CallIntMethod(env, pObj, getter); -} -#define VAL_GET_INT(OBJ) getOutputInt(env, OBJ) -#endif - static int encodingTypeIsValid(int eTextRep){ switch(eTextRep){ case SQLITE_UTF8: case SQLITE_UTF16: diff --git a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java index d6543baf57..afe2618a00 100644 --- a/ext/jni/src/org/sqlite/jni/NativePointerHolder.java +++ b/ext/jni/src/org/sqlite/jni/NativePointerHolder.java @@ -23,18 +23,11 @@ package org.sqlite.jni; 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. + These objects do not _own_ the pointer they refer to. They are + intended simply to communicate that pointer between C and Java. */ public class NativePointerHolder { - 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; } + //! Only set from JNI, where access permissions don't matter. + private long nativePointer = 0; + public final long getNativePointer(){ return nativePointer; } } diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index f4f2269a10..ee308ce669 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -25,12 +25,10 @@ package org.sqlite.jni; 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;} } } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index bdf89a3d01..f639f6ae7a 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -154,9 +154,6 @@ public final class SQLite3Jni { public static native int sqlite3_close_v2(@NotNull sqlite3 db); - //TODO? public static native int sqlite3_collation_needed(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const char*)); - //TODO? public static native int sqlite3_collation_needed16(sqlite3*,void*,void(*)(void*,sqlite3*,int eTextRep,const void*)); - public static native byte[] sqlite3_column_blob(@NotNull sqlite3_stmt stmt, int ndx); public static native int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx); @@ -214,7 +211,8 @@ public final class SQLite3Jni { the db if the db uses the default encoding of UTF-8. To extract _standard_ UTF-8, use sqlite3_column_text_utf8(). - This API includes no functions for working with Modified UTF-8. + This API includes no functions for working with Java's Modified + UTF-8. [^1]: https://stackoverflow.com/questions/7921016 */ @@ -226,8 +224,43 @@ public final class SQLite3Jni { */ public static native byte[] sqlite3_column_text_utf8(@NotNull sqlite3_stmt stmt, int ndx); - //TODO public static native ?type? sqlite3_column_text16(@NotNull sqlite3_stmt stmt, int ndx); - //TODO: public static Object sqlite3_column_to_java(@NotNull sqlite3_value v){...} + + // The real utility of this function is questionable. + // /** + // Returns a Java value representation based on the value of + // sqlite_value_type(). For integer types it returns either Integer + // or Long, depending on whether the value will fit in an + // Integer. For floating-point values it always returns type Double. + + // If the column was bound using sqlite3_result_java_object() then + // that value, as an Object, is returned. + // */ + // public static Object sqlite3_column_to_java(@NotNull sqlite3_stmt stmt, + // int ndx){ + // sqlite3_value v = sqlite3_column_value(stmt, ndx); + // Object rv = null; + // if(null == v) return v; + // v = sqlite3_value_dup(v)/*need a protected value*/; + // if(null == v) return v /* OOM error in C */; + // if(112/* 'p' */ == sqlite3_value_subtype(v)){ + // rv = sqlite3_value_java_object(v); + // }else{ + // switch(sqlite3_value_type(v)){ + // case SQLITE_INTEGER: { + // final long i = sqlite3_value_int64(v); + // rv = (i<=0x7fffffff && i>=-0x7fffffff-1) + // ? new Integer((int)i) : new Long(i); + // break; + // } + // case SQLITE_FLOAT: rv = new Double(sqlite3_value_double(v)); break; + // case SQLITE_BLOB: rv = sqlite3_value_blob(v); break; + // case SQLITE_TEXT: rv = sqlite3_value_text(v); break; + // default: break; + // } + // } + // sqlite3_value_free(v); + // return rv; + // } public static native int sqlite3_column_type(@NotNull sqlite3_stmt stmt, int ndx); @@ -476,6 +509,8 @@ public final class SQLite3Jni { allocate such strings and store them somewhere for long-term use (leaking them more likely than not). Even then, passing around a pointer via Java like that has little practical use. + + Note that there is no sqlite3_bind_java_object() counterpart. */ public static native void sqlite3_result_java_object(@NotNull sqlite3_context cx, @NotNull Object o); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 287b61a76a..30b136d474 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -483,7 +483,7 @@ public class Tester1 { private static void testUdfJavaObject(){ final sqlite3 db = createNewDb(); - final ValueHolder testResult = new ValueHolder<>(42L); + final ValueHolder testResult = new ValueHolder<>(db); SQLFunction func = new SQLFunction.Scalar(){ public void xFunc(sqlite3_context cx, sqlite3_value args[]){ sqlite3_result_java_object(cx, testResult.value); @@ -494,14 +494,16 @@ public class Tester1 { sqlite3_stmt stmt = new sqlite3_stmt(); sqlite3_prepare(db, "select myfunc()", stmt); affirm( 0 != stmt.getNativePointer() ); + affirm( testResult.value == db ); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ - sqlite3_value v = sqlite3_column_value(stmt, 0); + final sqlite3_value v = sqlite3_column_value(stmt, 0); affirm( testResult.value == sqlite3_value_java_object(v) ); - affirm( testResult.value == sqlite3_value_java_casted(v, Long.class) ); + affirm( testResult.value == sqlite3_value_java_casted(v, sqlite3.class) ); affirm( testResult.value == sqlite3_value_java_casted(v, testResult.value.getClass()) ); - affirm( null == sqlite3_value_java_casted(v, Double.class) ); + affirm( testResult.value == sqlite3_value_java_casted(v, Object.class) ); + affirm( null == sqlite3_value_java_casted(v, String.class) ); ++n; } sqlite3_finalize(stmt); diff --git a/manifest b/manifest index 4ba95e2df1..7c0eac5fdf 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_collation_needed()\sto\sJNI.\sRelated\sadjacent\scleanups\sand\sfixes. -D 2023-07-30T11:36:41.439 +C Minor\stweaks\sand\soptimizations\sin\sthe\sJNI\sbindings. +D 2023-07-30T13:30:52.663 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,19 +232,19 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 1934a72f33fe356d8af810a8a662dd8109026cd0bbf298dda1fe8bd1146603ad +F ext/jni/src/c/sqlite3-jni.c d3ce5d96feb5eebf8dd171f041704798f3d0a5da1ee93a43788059d1d9f167ff F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a -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/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee +F ext/jni/src/org/sqlite/jni/OutputPointer.java a5cb651df3b3adb65a9aca6cf9a094dea1346fc9ee5f341f79276348ac268351 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2c4564b19f5366927c9a5062e36ffb7744e7f69d00b3f8ce35fe59b2f3d60698 -F ext/jni/src/org/sqlite/jni/Tester1.java a89a87f8debd89f3488a65cb42af8e14fb0150b05d5a4a3592fb86d0cfda3287 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 42ca7686d009a56e4f5ceb74a0bd32ca69c025f2bf30d3e906696ad36ac72510 +F ext/jni/src/org/sqlite/jni/Tester1.java 1690172fccafbf8d8170b55b950003db182265c26dbb5a510122ec46a44d2611 F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7ac6614e69b03304d09745619ed83f12c7eb775aaf4a636a79289b01642ddd14 -R fe2ec7cfe7eced93fd3b168114e0d2e0 +P 16ff167691733350907d2d995c774a885214acd0fe8ec491c16b786f00fe85d4 +R fed5a38c4ce39785114a9bf68d9f61c4 U stephan -Z 9246dbb52619ce19a9defcf2e690b44f +Z a065a6cc3e3d2dbbd261c22dc07adbce # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index dce9f4cf7d..8c31c2b9aa 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -16ff167691733350907d2d995c774a885214acd0fe8ec491c16b786f00fe85d4 \ No newline at end of file +41fb5898f1a78d9fd85a020e28a6048a7359b54e35632e9072917cbdbcd8b07d \ No newline at end of file From 72bb29283fc2e732453a2bb7fc7ba7cc35325b48 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 13:47:51 +0000 Subject: [PATCH 026/148] Mark the Java wrapper classes of the C-native types as 'final' (cannot be subclassed). FossilOrigin-Name: 4fd3d93623d67c25fb8a490e0d4ea56d531d858067011ab1b28cce694098feff --- ext/jni/src/org/sqlite/jni/OutputPointer.java | 2 ++ ext/jni/src/org/sqlite/jni/sqlite3.java | 5 +---- .../src/org/sqlite/jni/sqlite3_context.java | 6 +----- ext/jni/src/org/sqlite/jni/sqlite3_stmt.java | 5 +---- ext/jni/src/org/sqlite/jni/sqlite3_value.java | 5 +---- manifest | 20 +++++++++---------- manifest.uuid | 2 +- 7 files changed, 17 insertions(+), 28 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index ee308ce669..d056b1b7fa 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -24,10 +24,12 @@ package org.sqlite.jni; */ public final class OutputPointer { public static final class Int32 { + //! Only set from the JNI layer. private int value; public final int getValue(){return value;} } public static final class Int64 { + //! Only set from the JNI layer. private long value; public final long getValue(){return value;} } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java index da1c00a966..99d06af829 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -19,8 +19,5 @@ package org.sqlite.jni; simply provide a type-safe way to communicate it between Java and C via JNI. */ -public class sqlite3 extends NativePointerHolder { - public sqlite3() { - super(); - } +public final class sqlite3 extends NativePointerHolder { } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_context.java b/ext/jni/src/org/sqlite/jni/sqlite3_context.java index 3272465299..a61ff21c7e 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_context.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_context.java @@ -17,11 +17,7 @@ package org.sqlite.jni; sqlite3_context instances are used in conjunction with user-defined SQL functions (a.k.a. UDFs). */ -public class sqlite3_context extends NativePointerHolder { - public sqlite3_context() { - super(); - } - +public final class sqlite3_context extends NativePointerHolder { /** For use only by the JNI layer. It's permitted to set this even though it's private. diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java index 2b5bbd54d0..fa19572b13 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java @@ -19,8 +19,5 @@ package org.sqlite.jni; simply provide a type-safe way to communicate it between Java and C via JNI. */ -public class sqlite3_stmt extends NativePointerHolder { - public sqlite3_stmt() { - super(); - } +public final class sqlite3_stmt extends NativePointerHolder { } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/sqlite3_value.java index 41347d8785..8aa8c77bbd 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_value.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_value.java @@ -13,8 +13,5 @@ */ package org.sqlite.jni; -public class sqlite3_value extends NativePointerHolder { - public sqlite3_value() { - super(); - } +public final class sqlite3_value extends NativePointerHolder { } diff --git a/manifest b/manifest index 7c0eac5fdf..551162e61f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\stweaks\sand\soptimizations\sin\sthe\sJNI\sbindings. -D 2023-07-30T13:30:52.663 +C Mark\sthe\sJava\swrapper\sclasses\sof\sthe\sC-native\stypes\sas\s'final'\s(cannot\sbe\ssubclassed). +D 2023-07-30T13:47:51.705 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -239,7 +239,7 @@ F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java a5cb651df3b3adb65a9aca6cf9a094dea1346fc9ee5f341f79276348ac268351 +F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd7e02592a3734df8887a22a9f74b12751 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 @@ -248,10 +248,10 @@ F ext/jni/src/org/sqlite/jni/Tester1.java 1690172fccafbf8d8170b55b950003db182265 F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/sqlite3.java 4058fbd63eb7085b5dc2daef4130623f464efdc838aafab8b9a4808c7cb01b6b -F ext/jni/src/org/sqlite/jni/sqlite3_context.java 841ac0384ec23e7d24ad9a928f8728b98bd3c4c3814d401200c6531786b9c241 -F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java f602b12521a66992299ca2877260d87bc69176b1bb05201f3b46825cb3cba315 -F ext/jni/src/org/sqlite/jni/sqlite3_value.java f9d8c0766b1d1b290564cb35db8d37be54c42adc8df22ee77b8d39e3e93398cd +F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 +F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 +F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 72a0698aeb50a183ad146cd29ee04952abb8c36021f6122656aa5ec20469f6f7 +F ext/jni/src/org/sqlite/jni/sqlite3_value.java fd045a09458e0a1b9328b085015b5ca5cc9024e7f91ee299f95da9dfd9a865a7 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 16ff167691733350907d2d995c774a885214acd0fe8ec491c16b786f00fe85d4 -R fed5a38c4ce39785114a9bf68d9f61c4 +P 41fb5898f1a78d9fd85a020e28a6048a7359b54e35632e9072917cbdbcd8b07d +R 5feab867f7054c182eed540fcab703e8 U stephan -Z a065a6cc3e3d2dbbd261c22dc07adbce +Z deb68695eee2433aa355b2bf6487020e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8c31c2b9aa..3d12cbe792 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -41fb5898f1a78d9fd85a020e28a6048a7359b54e35632e9072917cbdbcd8b07d \ No newline at end of file +4fd3d93623d67c25fb8a490e0d4ea56d531d858067011ab1b28cce694098feff \ No newline at end of file From 91710673e7961199f9a67d5b727d94b8f71ce2f6 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 17:24:01 +0000 Subject: [PATCH 027/148] Internal JNI refactoring towards consolidating support for callbacks with and without finalizers. FossilOrigin-Name: 120983a570d6de055cef9d916096de3410897ea9f46d23ea6eff1f9b549e423a --- ext/jni/src/c/sqlite3-jni.c | 104 ++++++++++++--------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 7 +- manifest | 14 +-- manifest.uuid | 2 +- 4 files changed, 68 insertions(+), 59 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 8db9416394..6e65313f8f 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -332,23 +332,27 @@ static void JNIEnvCache_clear(JNIEnvCache * p){ memset(p, 0, sizeof(JNIEnvCache)); } -/** - State for binding Java-side busy handlers. -*/ -typedef struct { - JNIEnv * env; /* env registered from */; - jobject jObj /* BusyHandler instance */; - jclass klazz /* jObj's class */; - jmethodID jmidxCallback /* klazz's xCallback method */; -} BusyHandlerJni; - /** State for various hook callbacks. */ typedef struct JniHookState JniHookState; struct JniHookState{ - jobject jObj; - jmethodID midCallback; + jobject jObj /* global ref to Java instance */; + jmethodID midCallback /* callback method */; }; +/** + State for binding Java-side callbacks which potentially have an + xDestroy() method. Maintenance reminder: this is different from + JniHookState because of the need to look up the finalizer. TODO: + look into consolidating this with JniHookState, perhaps adding the + jclass member to that object. +*/ +typedef struct JniHookStateWithDtor JniHookStateWithDtor; +struct JniHookStateWithDtor{ + JniHookState base; + jclass klazz /* jObj's class */; +}; + + /** Per-(sqlite3*) state for bindings which do not have their own finalizer functions, e.g. tracing and commit/rollback hooks. This @@ -381,7 +385,7 @@ struct PerDbStateJni { JniHookState rollbackHook; JniHookState updateHook; JniHookState collationNeeded; - BusyHandlerJni busyHandler; + JniHookStateWithDtor busyHandler; }; static struct { @@ -455,38 +459,39 @@ static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ any exceptions it throws. This is a no-op of s has no current state. */ -static void BusyHandlerJni_clear(BusyHandlerJni * const s){ - if(s->jObj){ - JNIEnv * const env = s->env; +static void JniHookStateWithDtor_clear(JNIEnv *env, JniHookStateWithDtor * const s){ + if(s->base.jObj){ const jmethodID method = (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); if(method){ - (*env)->CallVoidMethod(env, s->jObj, method); - IFTHREW_CLEAR; + (*env)->CallVoidMethod(env, s->base.jObj, method); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_CLEAR; + } }else{ EXCEPTION_CLEAR; } - UNREF_G(s->jObj); + UNREF_G(s->base.jObj); UNREF_G(s->klazz); - memset(s, 0, sizeof(BusyHandlerJni)); + memset(s, 0, sizeof(JniHookStateWithDtor)); } } /** - Initializes s to wrap BusyHandlerJni-type object jObject, clearning + Initializes s to wrap JniHookStateWithDtor-type object jObject, clearing any current state of s beforehand. Returns 0 on success, non-0 on error. On error, s's state is cleared. */ -static int BusyHandlerJni_init(JNIEnv * const env, BusyHandlerJni * const s, +static int JniHookStateWithDtor_init(JNIEnv * const env, JniHookStateWithDtor * const s, jobject jObj){ const char * zSig = "(I)I" /* callback signature */; - if(s->jObj) BusyHandlerJni_clear(s); - s->env = env; - s->jObj = REF_G(jObj); + if(s->base.jObj) JniHookStateWithDtor_clear(env, s); + s->base.jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - s->jmidxCallback = (*env)->GetMethodID(env, s->klazz, "xCallback", zSig); + s->base.midCallback = (*env)->GetMethodID(env, s->klazz, "xCallback", zSig); IFTHREW { - BusyHandlerJni_clear(s); + JniHookStateWithDtor_clear(env, s); return SQLITE_ERROR; } return 0; @@ -702,6 +707,11 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb, jobject jD return rv; } +static void JniHookState_unref(JNIEnv * const env, JniHookState * const s){ + UNREF_G(s->jObj); + //UNREF_G_(s->klazz); +} + /** Clears s's state and moves it to the free-list. */ @@ -720,12 +730,15 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ assert(!s->pPrev); S3Global.perDb.aUsed = s->pNext; } - UNREF_G(s->trace.jObj); - UNREF_G(s->progress.jObj); - UNREF_G(s->commitHook.jObj); - UNREF_G(s->rollbackHook.jObj); +#define UNHOOK(MEMBER) JniHookState_unref(env, &s->MEMBER) + UNHOOK(trace); + UNHOOK(progress); + UNHOOK(commitHook); + UNHOOK(rollbackHook); + UNHOOK(updateHook); +#undef UNHOOK UNREF_G(s->jDb); - BusyHandlerJni_clear(&s->busyHandler); + JniHookStateWithDtor_clear(env, &s->busyHandler); memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; @@ -742,8 +755,7 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ MARKER(("PerDbStateJni->progress.jObj @ %p\n", s->progress.jObj)); MARKER(("PerDbStateJni->commitHook.jObj @ %p\n", s->commitHook.jObj)); MARKER(("PerDbStateJni->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("PerDbStateJni->busyHandler.env @ %p\n", s->busyHandler.env)); - MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); + MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.base.jObj)); MARKER(("PerDbStateJni->env @ %p\n", s->env)); } @@ -1392,10 +1404,10 @@ JDECL(jint,1bind_1zeroblob64)(JENV_JSELF, jobject jpStmt, static int s3jni_busy_handler(void* pState, int n){ PerDbStateJni * const ps = (PerDbStateJni *)pState; int rc = 0; - if( ps->busyHandler.jObj ){ + if( ps->busyHandler.base.jObj ){ JNIEnv * const env = ps->env; - rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, - ps->busyHandler.jmidxCallback, (jint)n); + rc = (*env)->CallIntMethod(env, ps->busyHandler.base.jObj, + ps->busyHandler.base.midCallback, (jint)n); IFTHREW_CLEAR; } return rc; @@ -1406,20 +1418,20 @@ JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ int rc; if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ - if(ps->busyHandler.jObj && - (*env)->IsSameObject(env, ps->busyHandler.jObj, jBusy)){ + if(ps->busyHandler.base.jObj && + (*env)->IsSameObject(env, ps->busyHandler.base.jObj, jBusy)){ /* Same object - this is a no-op. */ return 0; } - rc = BusyHandlerJni_init(env, &ps->busyHandler, jBusy); + rc = JniHookStateWithDtor_init(env, &ps->busyHandler, jBusy); if(rc){ - assert(!ps->busyHandler.jObj); + assert(!ps->busyHandler.base.jObj); return (jint)rc; } - assert(ps->busyHandler.jObj && ps->busyHandler.klazz); - assert( (*env)->IsSameObject(env, ps->busyHandler.jObj, jBusy) ); + assert(ps->busyHandler.base.jObj && ps->busyHandler.klazz); + assert( (*env)->IsSameObject(env, ps->busyHandler.base.jObj, jBusy) ); }else{ - BusyHandlerJni_clear(&ps->busyHandler); + JniHookStateWithDtor_clear(env, &ps->busyHandler); } return jBusy ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) @@ -1429,8 +1441,8 @@ JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); if( ps ){ - if( ps->busyHandler.jObj ){ - BusyHandlerJni_clear(&ps->busyHandler); + if( ps->busyHandler.base.jObj ){ + JniHookStateWithDtor_clear(env, &ps->busyHandler); } return sqlite3_busy_timeout(ps->pDb, (int)ms); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index f639f6ae7a..b41d011ea3 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -20,8 +20,8 @@ import java.lang.annotation.ElementType; /** This annotation is for flagging parameters which may legally be - null, noting that they may behave different if passed null but are - prepared to expect null as a value. + null, noting that they may behave differently if passed null but + are prepared to expect null as a value. This annotation is solely for the reader's information. */ @@ -271,9 +271,6 @@ public final class SQLite3Jni { public static native int sqlite3_collation_needed(@NotNull sqlite3 db, @Nullable CollationNeeded callback); - //TODO public static native int sqlite3_collation_needed16( - // sqlite3 db, void(*)(void*,sqlite3*,int eTextRep,const void*) - public static native sqlite3 sqlite3_context_db_handle(@NotNull sqlite3_context cx); public static native CommitHook sqlite3_commit_hook(@NotNull sqlite3 db, @Nullable CommitHook hook); diff --git a/manifest b/manifest index 551162e61f..99776231d2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Mark\sthe\sJava\swrapper\sclasses\sof\sthe\sC-native\stypes\sas\s'final'\s(cannot\sbe\ssubclassed). -D 2023-07-30T13:47:51.705 +C Internal\sJNI\srefactoring\stowards\sconsolidating\ssupport\sfor\scallbacks\swith\sand\swithout\sfinalizers. +D 2023-07-30T17:24:01.771 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c d3ce5d96feb5eebf8dd171f041704798f3d0a5da1ee93a43788059d1d9f167ff +F ext/jni/src/c/sqlite3-jni.c 5334b9a85288fbe2750234001f0054a700d8b3e996b7353c03f1f0231a55ac6f F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -243,7 +243,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 42ca7686d009a56e4f5ceb74a0bd32ca69c025f2bf30d3e906696ad36ac72510 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 829a6409d6fa5e82b39d85df5f14643c93dff383978ccbf35dc4af3ba29b6e62 F ext/jni/src/org/sqlite/jni/Tester1.java 1690172fccafbf8d8170b55b950003db182265c26dbb5a510122ec46a44d2611 F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 41fb5898f1a78d9fd85a020e28a6048a7359b54e35632e9072917cbdbcd8b07d -R 5feab867f7054c182eed540fcab703e8 +P 4fd3d93623d67c25fb8a490e0d4ea56d531d858067011ab1b28cce694098feff +R bc4ee4f83b4e96887d3ad46ea5a5d938 U stephan -Z deb68695eee2433aa355b2bf6487020e +Z ea19c30c280d92414c0f0555fd56a50e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3d12cbe792..f0ccce7b86 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4fd3d93623d67c25fb8a490e0d4ea56d531d858067011ab1b28cce694098feff \ No newline at end of file +120983a570d6de055cef9d916096de3410897ea9f46d23ea6eff1f9b549e423a \ No newline at end of file From 09947d0aad6a56a61b99a97cd8a18dd110722891 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 30 Jul 2023 18:41:25 +0000 Subject: [PATCH 028/148] Internal JNI API renaming. FossilOrigin-Name: fcfc070673cef2f657f4737f096678439ed7c011fb2e5391e0721f82f5d8af51 --- ext/jni/src/c/sqlite3-jni.c | 26 +++++++++++++------------- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 6e65313f8f..bf5118bf64 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -346,8 +346,8 @@ struct JniHookState{ look into consolidating this with JniHookState, perhaps adding the jclass member to that object. */ -typedef struct JniHookStateWithDtor JniHookStateWithDtor; -struct JniHookStateWithDtor{ +typedef struct BusyHandlerJni BusyHandlerJni; +struct BusyHandlerJni{ JniHookState base; jclass klazz /* jObj's class */; }; @@ -385,7 +385,7 @@ struct PerDbStateJni { JniHookState rollbackHook; JniHookState updateHook; JniHookState collationNeeded; - JniHookStateWithDtor busyHandler; + BusyHandlerJni busyHandler; }; static struct { @@ -459,7 +459,7 @@ static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ any exceptions it throws. This is a no-op of s has no current state. */ -static void JniHookStateWithDtor_clear(JNIEnv *env, JniHookStateWithDtor * const s){ +static void BusyHandlerJni_clear(JNIEnv *env, BusyHandlerJni * const s){ if(s->base.jObj){ const jmethodID method = (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); @@ -474,24 +474,24 @@ static void JniHookStateWithDtor_clear(JNIEnv *env, JniHookStateWithDtor * const } UNREF_G(s->base.jObj); UNREF_G(s->klazz); - memset(s, 0, sizeof(JniHookStateWithDtor)); + memset(s, 0, sizeof(BusyHandlerJni)); } } /** - Initializes s to wrap JniHookStateWithDtor-type object jObject, clearing + Initializes s to wrap BusyHandlerJni-type object jObject, clearing any current state of s beforehand. Returns 0 on success, non-0 on error. On error, s's state is cleared. */ -static int JniHookStateWithDtor_init(JNIEnv * const env, JniHookStateWithDtor * const s, +static int BusyHandlerJni_init(JNIEnv * const env, BusyHandlerJni * const s, jobject jObj){ const char * zSig = "(I)I" /* callback signature */; - if(s->base.jObj) JniHookStateWithDtor_clear(env, s); + if(s->base.jObj) BusyHandlerJni_clear(env, s); s->base.jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); s->base.midCallback = (*env)->GetMethodID(env, s->klazz, "xCallback", zSig); IFTHREW { - JniHookStateWithDtor_clear(env, s); + BusyHandlerJni_clear(env, s); return SQLITE_ERROR; } return 0; @@ -738,7 +738,7 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ UNHOOK(updateHook); #undef UNHOOK UNREF_G(s->jDb); - JniHookStateWithDtor_clear(env, &s->busyHandler); + BusyHandlerJni_clear(env, &s->busyHandler); memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; @@ -1423,7 +1423,7 @@ JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ /* Same object - this is a no-op. */ return 0; } - rc = JniHookStateWithDtor_init(env, &ps->busyHandler, jBusy); + rc = BusyHandlerJni_init(env, &ps->busyHandler, jBusy); if(rc){ assert(!ps->busyHandler.base.jObj); return (jint)rc; @@ -1431,7 +1431,7 @@ JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ assert(ps->busyHandler.base.jObj && ps->busyHandler.klazz); assert( (*env)->IsSameObject(env, ps->busyHandler.base.jObj, jBusy) ); }else{ - JniHookStateWithDtor_clear(env, &ps->busyHandler); + BusyHandlerJni_clear(env, &ps->busyHandler); } return jBusy ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) @@ -1442,7 +1442,7 @@ JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); if( ps ){ if( ps->busyHandler.base.jObj ){ - JniHookStateWithDtor_clear(env, &ps->busyHandler); + BusyHandlerJni_clear(env, &ps->busyHandler); } return sqlite3_busy_timeout(ps->pDb, (int)ms); } diff --git a/manifest b/manifest index 99776231d2..795dd4ed31 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sJNI\srefactoring\stowards\sconsolidating\ssupport\sfor\scallbacks\swith\sand\swithout\sfinalizers. -D 2023-07-30T17:24:01.771 +C Internal\sJNI\sAPI\srenaming. +D 2023-07-30T18:41:25.803 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 5334b9a85288fbe2750234001f0054a700d8b3e996b7353c03f1f0231a55ac6f +F ext/jni/src/c/sqlite3-jni.c e1dc9820a9a68fad2d06e15022e4288817a29ca83bf804e3926985115f1f1a1d F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4fd3d93623d67c25fb8a490e0d4ea56d531d858067011ab1b28cce694098feff -R bc4ee4f83b4e96887d3ad46ea5a5d938 +P 120983a570d6de055cef9d916096de3410897ea9f46d23ea6eff1f9b549e423a +R 6a2910fdd19645a78e675ea1b5f3006c U stephan -Z ea19c30c280d92414c0f0555fd56a50e +Z 307d46c1a84b2aab03be2e0da79180c9 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f0ccce7b86..68ed83bbaa 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -120983a570d6de055cef9d916096de3410897ea9f46d23ea6eff1f9b549e423a \ No newline at end of file +fcfc070673cef2f657f4737f096678439ed7c011fb2e5391e0721f82f5d8af51 \ No newline at end of file From 9c1c6da93018feadbc4b4394d75cbbf4a615797c Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 07:15:25 +0000 Subject: [PATCH 029/148] Consolidate triplicated xDestroy()-calling code. Remove some unnecessary casts. FossilOrigin-Name: 24c0763d0e025187c74002ffee11fd48d3cd7b40e01469d28484bb67f701884b --- ext/jni/src/c/sqlite3-jni.c | 87 ++++++++++++++++++------------------- manifest | 12 ++--- manifest.uuid | 2 +- 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index bf5118bf64..c9f3b1df50 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -454,17 +454,22 @@ static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ } /** - Clears s's state, releasing any Java references. Before doing so, - it calls s's xDestroy() method, ignoring the lack of that method or - any exceptions it throws. This is a no-op of s has no current - state. + Extracts the (void xDestroy()) method from the given jclass and + applies it to jobj. If jobs is NULL, this is a no-op. If klazz is + NULL then it's derived from jobj. The lack of an xDestroy() method + is silently ignored and any exceptions thrown by the method trigger + a warning to stdout or stderr and then the exception is suppressed. */ -static void BusyHandlerJni_clear(JNIEnv *env, BusyHandlerJni * const s){ - if(s->base.jObj){ - const jmethodID method = - (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); +static void s3jni_call_xDestroy(JNIEnv *env, jobject jobj, jclass klazz){ + if(jobj){ + jmethodID method; + if(!klazz){ + klazz = (*env)->GetObjectClass(env, jobj); + assert(klazz); + } + method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); if(method){ - (*env)->CallVoidMethod(env, s->base.jObj, method); + (*env)->CallVoidMethod(env, jobj, method); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW; EXCEPTION_CLEAR; @@ -472,6 +477,19 @@ static void BusyHandlerJni_clear(JNIEnv *env, BusyHandlerJni * const s){ }else{ EXCEPTION_CLEAR; } + } +} + + +/** + Clears s's state, releasing any Java references. Before doing so, + it calls s's xDestroy() method, ignoring the lack of that method or + any exceptions it throws. This is a no-op of s has no current + state. +*/ +static void BusyHandlerJni_clear(JNIEnv *env, BusyHandlerJni * const s){ + if(s->base.jObj){ + s3jni_call_xDestroy(env, s->base.jObj, s->klazz); UNREF_G(s->base.jObj); UNREF_G(s->klazz); memset(s, 0, sizeof(BusyHandlerJni)); @@ -909,19 +927,19 @@ static CollationState * CollationState_alloc(void){ return rc; } -static void CollationState_free(CollationState * cs){ +static void CollationState_free(CollationState * const cs){ JNIEnv * const env = cs->env; if(env){ //MARKER(("Collation cleanup...\n")); - if(cs->oCollation) UNREF_G(cs->oCollation); - if(cs->klazz) UNREF_G(cs->klazz); + UNREF_G(cs->oCollation); + UNREF_G(cs->klazz); } sqlite3_free(cs); } -static int collation_xCompare_proxy(void *pArg, int nLhs, const void *lhs, - int nRhs, const void *rhs){ - CollationState * const cs = (CollationState*)pArg; +static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, + int nRhs, const void *rhs){ + CollationState * const cs = pArg; JNIEnv * env = cs->env; jint rc; jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); @@ -937,20 +955,10 @@ static int collation_xCompare_proxy(void *pArg, int nLhs, const void *lhs, return (int)rc; } -static void collation_xDestroy_proxy(void *pArg){ - CollationState * const cs = (CollationState*)pArg; - if(cs->oCollation){ - JNIEnv * const env = cs->env; - const jmethodID method = (*env)->GetMethodID(env, cs->klazz, "xDestroy", - "()V"); - //MARKER(("Calling Collation.xDestroy()...\n")); - (*env)->CallVoidMethod(env, cs->oCollation, method); - IFTHREW { - EXCEPTION_WARN_CALLBACK_THREW; - EXCEPTION_CLEAR; - } - //MARKER(("Returned from Collation.xDestroy().\n")); - } +/* Collation finalizer for use by the sqlite3 internals. */ +static void CollationState_xDestroy(void *pArg){ + CollationState * const cs = pArg; + s3jni_call_xDestroy(cs->env, cs->oCollation, cs->klazz); CollationState_free(cs); } @@ -1132,19 +1140,8 @@ static UDFState * UDFState_alloc(JNIEnv *env, jobject jObj){ static void UDFState_free(UDFState * s){ JNIEnv * const env = s->env; if(env){ - //MARKER(("Collation cleanup...\n")); - if(s->jObj){ - const jmethodID method = - (*env)->GetMethodID(env, s->klazz, "xDestroy", "()V"); - if(method){ - //MARKER(("aCalling SQLFunction.xDestroy()...\n")); - (*env)->CallVoidMethod(env, s->jObj, method); - EXCEPTION_IGNORE; - //MARKER(("Returned from SQLFunction.xDestroy().\n")); - }else{ - (*env)->ExceptionClear(env); - } - } + //MARKER(("UDF cleanup...\n")); + s3jni_call_xDestroy(env, s->jObj, s->klazz); UNREF_G(s->jObj); UNREF_G(s->klazz); } @@ -1734,10 +1731,10 @@ JDECL(jint,1create_1collation)(JENV_JSELF, jobject jpDb, "([B[B)I"); zName = JSTR_TOC(name); rc = sqlite3_create_collation_v2(PtrGet_sqlite3(jpDb), zName, (int)eTextRep, - cs, collation_xCompare_proxy, - collation_xDestroy_proxy); + cs, CollationState_xCompare, + CollationState_xDestroy); JSTR_RELEASE(name, zName); - if(0 != rc) collation_xDestroy_proxy(cs); + if(0 != rc) CollationState_xDestroy(cs); return (jint)rc; } diff --git a/manifest b/manifest index 795dd4ed31..4aa0d7abb8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sJNI\sAPI\srenaming. -D 2023-07-30T18:41:25.803 +C Consolidate\striplicated\sxDestroy()-calling\scode.\sRemove\ssome\sunnecessary\scasts. +D 2023-07-31T07:15:25.615 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c e1dc9820a9a68fad2d06e15022e4288817a29ca83bf804e3926985115f1f1a1d +F ext/jni/src/c/sqlite3-jni.c 2d61ba074e851ad010307fd901581030a3f234771c266306ff9ccf33ae1a5542 F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 120983a570d6de055cef9d916096de3410897ea9f46d23ea6eff1f9b549e423a -R 6a2910fdd19645a78e675ea1b5f3006c +P fcfc070673cef2f657f4737f096678439ed7c011fb2e5391e0721f82f5d8af51 +R cfdb6b4bc4a7852f0b4b2fc4d64b3f33 U stephan -Z 307d46c1a84b2aab03be2e0da79180c9 +Z 7456fbe378da7eba9d869800225a0ec8 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 68ed83bbaa..5d723d64c8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fcfc070673cef2f657f4737f096678439ed7c011fb2e5391e0721f82f5d8af51 \ No newline at end of file +24c0763d0e025187c74002ffee11fd48d3cd7b40e01469d28484bb67f701884b \ No newline at end of file From 41a7ac637ace941baf9b0ff51feb4b83ce4692b8 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 09:28:15 +0000 Subject: [PATCH 030/148] Internal JNI doc tweaks. FossilOrigin-Name: f4aa2c82882cb6be1fd52977de19fd03c2e38abb857b520f951b32d610972ab6 --- ext/jni/src/c/sqlite3-jni.c | 34 +++++++++++++++++++++++----------- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index c9f3b1df50..55484bb5d4 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -367,8 +367,8 @@ struct BusyHandlerJni{ */ typedef struct PerDbStateJni PerDbStateJni; struct PerDbStateJni { - JNIEnv *env; - sqlite3 * pDb; + JNIEnv *env /* The associated JNIEnv handle */; + sqlite3 *pDb /* The associated db handle */; jobject jDb /* a global ref of the object which was passed to sqlite3_open(_v2)(). We need this in order to have an object to pass to sqlite3_collation_needed()'s @@ -377,8 +377,8 @@ struct PerDbStateJni { would be a different instance (and maybe even a different class) than the one the user expects to receive. */; - PerDbStateJni * pNext; - PerDbStateJni * pPrev; + PerDbStateJni * pNext /* Next entry in the available/free list */; + PerDbStateJni * pPrev /* Previous entry in the available/free list */; JniHookState trace; JniHookState progress; JniHookState commitHook; @@ -941,10 +941,13 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ CollationState * const cs = pArg; JNIEnv * env = cs->env; - jint rc; + jint rc = 0; jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); - jbyteArray jbaRhs = (*env)->NewByteArray(env, (jint)nRhs); + jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); + if(!jbaRhs){ + (*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); + } (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); rc = (*env)->CallIntMethod(env, cs->oCollation, cs->midCompare, @@ -965,12 +968,21 @@ static void CollationState_xDestroy(void *pArg){ /* State for sqlite3_result_java_object() and sqlite3_value_java_object(). */ typedef struct { - /* POTENTIAL bug: the JNI docs say that the JNIEnv pointer - is guaranteed to resolve the same for the same contexts, - but the docs are unclear as to whether it's the (JNIEnv *env) - or (*env) which resolves consistently. + /* The JNI docs say: - This posts claims it's unsave to cache JNIEnv at all, even when + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html + + > The VM is guaranteed to pass the same interface pointer to a + native method when it makes multiple calls to the native method + from the same Java thread. + + Per the accompanying diagram, the "interface pointer" is the + pointer-to-pointer which is passed to all JNI calls + (`JNIEnv *env`), implying that we need to be caching that. The + verbiage "interface pointer" implies, however, that we should be + storing the dereferenced `(*env)` pointer. + + This posts claims it's unsafe to cache JNIEnv at all, even when it's always used in the same thread: https://stackoverflow.com/questions/12420463 diff --git a/manifest b/manifest index 4aa0d7abb8..ea0069b0c1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Consolidate\striplicated\sxDestroy()-calling\scode.\sRemove\ssome\sunnecessary\scasts. -D 2023-07-31T07:15:25.615 +C Internal\sJNI\sdoc\stweaks. +D 2023-07-31T09:28:15.075 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 2d61ba074e851ad010307fd901581030a3f234771c266306ff9ccf33ae1a5542 +F ext/jni/src/c/sqlite3-jni.c d92a945e490d05878da10449fcb8a1d50f04357557d409a85d53bbe27cb5c532 F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P fcfc070673cef2f657f4737f096678439ed7c011fb2e5391e0721f82f5d8af51 -R cfdb6b4bc4a7852f0b4b2fc4d64b3f33 +P 24c0763d0e025187c74002ffee11fd48d3cd7b40e01469d28484bb67f701884b +R 7bd2b6ba1e43d037c3e290268c0148cb U stephan -Z 7456fbe378da7eba9d869800225a0ec8 +Z 1e5d7f469bee5667fa6677ac776d2be7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5d723d64c8..dd90728405 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -24c0763d0e025187c74002ffee11fd48d3cd7b40e01469d28484bb67f701884b \ No newline at end of file +f4aa2c82882cb6be1fd52977de19fd03c2e38abb857b520f951b32d610972ab6 \ No newline at end of file From 46c46e400a6e57173df14b904cc03b4539128246 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 09:45:49 +0000 Subject: [PATCH 031/148] Refactor the collation-specific JNI hook type to use the generic hook type. FossilOrigin-Name: 02c1d3b6501fedf3d6e6d1ca60699df268522182c5ba3b49ae8f4691499ef0fc --- ext/jni/src/c/sqlite3-jni.c | 95 +++++++++++++++++-------------------- manifest | 12 ++--- manifest.uuid | 2 +- 3 files changed, 50 insertions(+), 59 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 55484bb5d4..b6e19a10a7 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -337,6 +337,7 @@ typedef struct JniHookState JniHookState; struct JniHookState{ jobject jObj /* global ref to Java instance */; jmethodID midCallback /* callback method */; + jclass klazz /* jObj's class. Not needed by all types. */; }; /** @@ -384,6 +385,7 @@ struct PerDbStateJni { JniHookState commitHook; JniHookState rollbackHook; JniHookState updateHook; + JniHookState collation; JniHookState collationNeeded; BusyHandlerJni busyHandler; }; @@ -725,9 +727,13 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb, jobject jD return rv; } -static void JniHookState_unref(JNIEnv * const env, JniHookState * const s){ +static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){ + if(doXDestroy && s->klazz && s->jObj){ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + } UNREF_G(s->jObj); - //UNREF_G_(s->klazz); + UNREF_G(s->klazz); + memset(s, 0, sizeof(JniHookState)); } /** @@ -748,12 +754,14 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ assert(!s->pPrev); S3Global.perDb.aUsed = s->pNext; } -#define UNHOOK(MEMBER) JniHookState_unref(env, &s->MEMBER) - UNHOOK(trace); - UNHOOK(progress); - UNHOOK(commitHook); - UNHOOK(rollbackHook); - UNHOOK(updateHook); +#define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY) + UNHOOK(trace, 0); + UNHOOK(progress, 0); + UNHOOK(commitHook, 0); + UNHOOK(rollbackHook, 0); + UNHOOK(updateHook, 0); + UNHOOK(collation, 1); + UNHOOK(collationNeeded, 1); #undef UNHOOK UNREF_G(s->jDb); BusyHandlerJni_clear(env, &s->busyHandler); @@ -911,36 +919,10 @@ static int encodingTypeIsValid(int eTextRep){ } } -/** - State for binding Java-side collation sequences. -*/ -typedef struct { - jclass klazz /* Collation object's class */; - jobject oCollation /* Collation instance */; - jmethodID midCompare /* cached xCompare */; - JNIEnv * env; /* env registered from */; -} CollationState; - -static CollationState * CollationState_alloc(void){ - CollationState * rc = sqlite3_malloc(sizeof(CollationState)); - if(rc) memset(rc, 0, sizeof(CollationState)); - return rc; -} - -static void CollationState_free(CollationState * const cs){ - JNIEnv * const env = cs->env; - if(env){ - //MARKER(("Collation cleanup...\n")); - UNREF_G(cs->oCollation); - UNREF_G(cs->klazz); - } - sqlite3_free(cs); -} - static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ - CollationState * const cs = pArg; - JNIEnv * env = cs->env; + PerDbStateJni * const ps = pArg; + JNIEnv * env = ps->env; jint rc = 0; jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; @@ -950,7 +932,7 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, } (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); - rc = (*env)->CallIntMethod(env, cs->oCollation, cs->midCompare, + rc = (*env)->CallIntMethod(env, ps->collation.jObj, ps->collation.midCallback, jbaLhs, jbaRhs); EXCEPTION_IGNORE; UNREF_L(jbaLhs); @@ -960,9 +942,8 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, /* Collation finalizer for use by the sqlite3 internals. */ static void CollationState_xDestroy(void *pArg){ - CollationState * const cs = pArg; - s3jni_call_xDestroy(cs->env, cs->oCollation, cs->klazz); - CollationState_free(cs); + PerDbStateJni * const ps = pArg; + JniHookState_unref( ps->env, &ps->collation, 1 ); } /* State for sqlite3_result_java_object() and @@ -1728,25 +1709,35 @@ JDECL(jobject,1context_1db_1handle)(JENV_JSELF, jobject jpCx){ return db ? new_sqlite3_wrapper(env, db) : NULL; } -JDECL(jint,1create_1collation)(JENV_JSELF, jobject jpDb, +JDECL(jint,1create_1collation)(JENV_JSELF, jobject jDb, jstring name, jint eTextRep, jobject oCollation){ - const jclass klazz = (*env)->GetObjectClass(env, oCollation); + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + jclass klazz; int rc; const char *zName; - CollationState * const cs = CollationState_alloc(); - if(!cs) return (jint)SQLITE_NOMEM; - cs->env = env; - cs->oCollation = REF_G(oCollation); - cs->klazz = REF_G(klazz); - cs->midCompare = (*env)->GetMethodID(env, klazz, "xCompare", - "([B[B)I"); + JniHookState * pHook; + if(!ps) return (jint)SQLITE_NOMEM; + pHook = &ps->collation; + klazz = (*env)->GetObjectClass(env, oCollation); + pHook->midCallback = (*env)->GetMethodID(env, klazz, "xCompare", + "([B[B)I"); + IFTHREW{ + EXCEPTION_REPORT; + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Could not get xCompare() method for object."); + } zName = JSTR_TOC(name); - rc = sqlite3_create_collation_v2(PtrGet_sqlite3(jpDb), zName, (int)eTextRep, - cs, CollationState_xCompare, + rc = sqlite3_create_collation_v2(ps->pDb, zName, (int)eTextRep, + ps, CollationState_xCompare, CollationState_xDestroy); JSTR_RELEASE(name, zName); - if(0 != rc) CollationState_xDestroy(cs); + if( 0==rc ){ + pHook->jObj = REF_G(oCollation); + pHook->klazz = REF_G(klazz); + }else{ + JniHookState_unref(env, pHook, 1); + } return (jint)rc; } diff --git a/manifest b/manifest index ea0069b0c1..cde7e0a83e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sJNI\sdoc\stweaks. -D 2023-07-31T09:28:15.075 +C Refactor\sthe\scollation-specific\sJNI\shook\stype\sto\suse\sthe\sgeneric\shook\stype. +D 2023-07-31T09:45:49.092 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c d92a945e490d05878da10449fcb8a1d50f04357557d409a85d53bbe27cb5c532 +F ext/jni/src/c/sqlite3-jni.c 31cf7df43d9c7e99431260e9ec0fbc622587e875ab22b3f4f6a262fd77bf16c8 F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 24c0763d0e025187c74002ffee11fd48d3cd7b40e01469d28484bb67f701884b -R 7bd2b6ba1e43d037c3e290268c0148cb +P f4aa2c82882cb6be1fd52977de19fd03c2e38abb857b520f951b32d610972ab6 +R 3fb71491075a1e3e33a1832011a5ad65 U stephan -Z 1e5d7f469bee5667fa6677ac776d2be7 +Z 6de14785676dadf1719d78be6df968a1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index dd90728405..690cb470a8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f4aa2c82882cb6be1fd52977de19fd03c2e38abb857b520f951b32d610972ab6 \ No newline at end of file +02c1d3b6501fedf3d6e6d1ca60699df268522182c5ba3b49ae8f4691499ef0fc \ No newline at end of file From 452108b4fc0efc688c86b41e3413b211f817d59c Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 10:08:36 +0000 Subject: [PATCH 032/148] Refactor the busy-handler-specific JNI hook type to use the generic hook type. FossilOrigin-Name: d9efdc6dd20a34bfdaad5d4bf8e67cce7e35238299eb91e4459d59fda11978a6 --- ext/jni/src/c/sqlite3-jni.c | 134 ++++++++++++++---------------------- manifest | 12 ++-- manifest.uuid | 2 +- 3 files changed, 59 insertions(+), 89 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index b6e19a10a7..454dacb55f 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -336,24 +336,14 @@ static void JNIEnvCache_clear(JNIEnvCache * p){ typedef struct JniHookState JniHookState; struct JniHookState{ jobject jObj /* global ref to Java instance */; - jmethodID midCallback /* callback method */; - jclass klazz /* jObj's class. Not needed by all types. */; + jmethodID midCallback /* callback method. Signature depends on + jObj's type */; + jclass klazz /* global ref to jObj's class. Only needed + by hooks which have an xDestroy() method, + as lookup of that method is deferred + until the object requires cleanup. */; }; -/** - State for binding Java-side callbacks which potentially have an - xDestroy() method. Maintenance reminder: this is different from - JniHookState because of the need to look up the finalizer. TODO: - look into consolidating this with JniHookState, perhaps adding the - jclass member to that object. -*/ -typedef struct BusyHandlerJni BusyHandlerJni; -struct BusyHandlerJni{ - JniHookState base; - jclass klazz /* jObj's class */; -}; - - /** Per-(sqlite3*) state for bindings which do not have their own finalizer functions, e.g. tracing and commit/rollback hooks. This @@ -380,14 +370,14 @@ struct PerDbStateJni { receive. */; PerDbStateJni * pNext /* Next entry in the available/free list */; PerDbStateJni * pPrev /* Previous entry in the available/free list */; - JniHookState trace; - JniHookState progress; - JniHookState commitHook; - JniHookState rollbackHook; - JniHookState updateHook; + JniHookState busyHandler; JniHookState collation; JniHookState collationNeeded; - BusyHandlerJni busyHandler; + JniHookState commitHook; + JniHookState progress; + JniHookState rollbackHook; + JniHookState trace; + JniHookState updateHook; }; static struct { @@ -457,21 +447,21 @@ static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ /** Extracts the (void xDestroy()) method from the given jclass and - applies it to jobj. If jobs is NULL, this is a no-op. If klazz is + applies it to jobj. If jObj is NULL, this is a no-op. If klazz is NULL then it's derived from jobj. The lack of an xDestroy() method is silently ignored and any exceptions thrown by the method trigger a warning to stdout or stderr and then the exception is suppressed. */ -static void s3jni_call_xDestroy(JNIEnv *env, jobject jobj, jclass klazz){ - if(jobj){ +static void s3jni_call_xDestroy(JNIEnv *env, jobject jObj, jclass klazz){ + if(jObj){ jmethodID method; if(!klazz){ - klazz = (*env)->GetObjectClass(env, jobj); + klazz = (*env)->GetObjectClass(env, jObj); assert(klazz); } method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); if(method){ - (*env)->CallVoidMethod(env, jobj, method); + (*env)->CallVoidMethod(env, jObj, method); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW; EXCEPTION_CLEAR; @@ -482,41 +472,6 @@ static void s3jni_call_xDestroy(JNIEnv *env, jobject jobj, jclass klazz){ } } - -/** - Clears s's state, releasing any Java references. Before doing so, - it calls s's xDestroy() method, ignoring the lack of that method or - any exceptions it throws. This is a no-op of s has no current - state. -*/ -static void BusyHandlerJni_clear(JNIEnv *env, BusyHandlerJni * const s){ - if(s->base.jObj){ - s3jni_call_xDestroy(env, s->base.jObj, s->klazz); - UNREF_G(s->base.jObj); - UNREF_G(s->klazz); - memset(s, 0, sizeof(BusyHandlerJni)); - } -} - -/** - Initializes s to wrap BusyHandlerJni-type object jObject, clearing - any current state of s beforehand. Returns 0 on success, non-0 on - error. On error, s's state is cleared. -*/ -static int BusyHandlerJni_init(JNIEnv * const env, BusyHandlerJni * const s, - jobject jObj){ - const char * zSig = "(I)I" /* callback signature */; - if(s->base.jObj) BusyHandlerJni_clear(env, s); - s->base.jObj = REF_G(jObj); - s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - s->base.midCallback = (*env)->GetMethodID(env, s->klazz, "xCallback", zSig); - IFTHREW { - BusyHandlerJni_clear(env, s); - return SQLITE_ERROR; - } - return 0; -} - /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized @@ -727,13 +682,22 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb, jobject jD return rv; } +/** + Removes any Java references from s and clears its state. If + doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's + s is passed to s3jni_call_xDestroy() before any references are + cleared. It is legal to call this when the object has no Java + references. +*/ static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){ if(doXDestroy && s->klazz && s->jObj){ s3jni_call_xDestroy(env, s->jObj, s->klazz); } UNREF_G(s->jObj); UNREF_G(s->klazz); - memset(s, 0, sizeof(JniHookState)); + s->jObj = 0; + s->klazz = 0; + s->midCallback = 0; } /** @@ -762,9 +726,9 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ UNHOOK(updateHook, 0); UNHOOK(collation, 1); UNHOOK(collationNeeded, 1); + UNHOOK(busyHandler, 1); #undef UNHOOK UNREF_G(s->jDb); - BusyHandlerJni_clear(env, &s->busyHandler); memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; @@ -781,7 +745,7 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ MARKER(("PerDbStateJni->progress.jObj @ %p\n", s->progress.jObj)); MARKER(("PerDbStateJni->commitHook.jObj @ %p\n", s->commitHook.jObj)); MARKER(("PerDbStateJni->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.base.jObj)); + MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); MARKER(("PerDbStateJni->env @ %p\n", s->env)); } @@ -1394,34 +1358,42 @@ JDECL(jint,1bind_1zeroblob64)(JENV_JSELF, jobject jpStmt, static int s3jni_busy_handler(void* pState, int n){ PerDbStateJni * const ps = (PerDbStateJni *)pState; int rc = 0; - if( ps->busyHandler.base.jObj ){ + if( ps->busyHandler.jObj ){ JNIEnv * const env = ps->env; - rc = (*env)->CallIntMethod(env, ps->busyHandler.base.jObj, - ps->busyHandler.base.midCallback, (jint)n); - IFTHREW_CLEAR; + rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, + ps->busyHandler.midCallback, (jint)n); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_CLEAR; + rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); + } } return rc; } JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); - int rc; + int rc = 0; if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ - if(ps->busyHandler.base.jObj && - (*env)->IsSameObject(env, ps->busyHandler.base.jObj, jBusy)){ + JniHookState * const pHook = &ps->busyHandler; + if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ /* Same object - this is a no-op. */ return 0; } - rc = BusyHandlerJni_init(env, &ps->busyHandler, jBusy); - if(rc){ - assert(!ps->busyHandler.base.jObj); - return (jint)rc; + JniHookState_unref(env, pHook, 1); + pHook->jObj = REF_G(jBusy); + pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); + pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); + IFTHREW { + JniHookState_unref(env, pHook, 0); + rc = SQLITE_ERROR; + } + if(rc){ + return rc; } - assert(ps->busyHandler.base.jObj && ps->busyHandler.klazz); - assert( (*env)->IsSameObject(env, ps->busyHandler.base.jObj, jBusy) ); }else{ - BusyHandlerJni_clear(env, &ps->busyHandler); + JniHookState_unref(env, &ps->busyHandler, 1); } return jBusy ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) @@ -1431,9 +1403,7 @@ JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); if( ps ){ - if( ps->busyHandler.base.jObj ){ - BusyHandlerJni_clear(env, &ps->busyHandler); - } + JniHookState_unref(env, &ps->busyHandler, 1); return sqlite3_busy_timeout(ps->pDb, (int)ms); } return SQLITE_MISUSE; diff --git a/manifest b/manifest index cde7e0a83e..9324f7b684 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactor\sthe\scollation-specific\sJNI\shook\stype\sto\suse\sthe\sgeneric\shook\stype. -D 2023-07-31T09:45:49.092 +C Refactor\sthe\sbusy-handler-specific\sJNI\shook\stype\sto\suse\sthe\sgeneric\shook\stype. +D 2023-07-31T10:08:36.744 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 56a014dbff9516774d895ec1ae9df0ed442765b556f79a0fc0b5bc438217200d F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 31cf7df43d9c7e99431260e9ec0fbc622587e875ab22b3f4f6a262fd77bf16c8 +F ext/jni/src/c/sqlite3-jni.c 9dc18b6eec43132aa9a5001bc12ddd29c69513a4c4d04717b4131a16b6782906 F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f4aa2c82882cb6be1fd52977de19fd03c2e38abb857b520f951b32d610972ab6 -R 3fb71491075a1e3e33a1832011a5ad65 +P 02c1d3b6501fedf3d6e6d1ca60699df268522182c5ba3b49ae8f4691499ef0fc +R 5b96073cb268292054640619b4e4117b U stephan -Z 6de14785676dadf1719d78be6df968a1 +Z 2c77c8dd9c75c9f80fb562e45a42818f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 690cb470a8..8ffd0dc238 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -02c1d3b6501fedf3d6e6d1ca60699df268522182c5ba3b49ae8f4691499ef0fc \ No newline at end of file +d9efdc6dd20a34bfdaad5d4bf8e67cce7e35238299eb91e4459d59fda11978a6 \ No newline at end of file From e2e696d11f6d1b243088112d45f8e019de6bead0 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 10:22:34 +0000 Subject: [PATCH 033/148] Update some internal docs for the past two checkins. Add a way to dump out some debug info about the current JNI environment. FossilOrigin-Name: ac9b8bb1e64450d980e2986084996549ae5c59e68c9f0c4c69539c239b64468b --- ext/jni/GNUmakefile | 3 +- ext/jni/src/c/sqlite3-jni.c | 41 ++++++++++++---------- ext/jni/src/c/sqlite3-jni.h | 8 +++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 7 ++++ ext/jni/src/org/sqlite/jni/Tester1.java | 3 +- manifest | 20 +++++------ manifest.uuid | 2 +- 7 files changed, 52 insertions(+), 32 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 31a5e85036..b313775f2b 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -147,7 +147,8 @@ 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 + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) $(package.jar): $(CLASS_FILES) $(MAKEFILE) rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 454dacb55f..f632cd6aef 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -345,29 +345,22 @@ struct JniHookState{ }; /** - Per-(sqlite3*) state for bindings which do not have their own - finalizer functions, e.g. tracing and commit/rollback hooks. This - state is allocated as needed, cleaned up in sqlite3_close(_v2)(), - and recycled when possible. It is freed during sqlite3_shutdown(). - - Open questions: - - - Do we need to do a (JNIEnv*) for the db and each set of binding - data (since they can(?) hypothetically be set via multiple JNIEnv - objects)? + Per-(sqlite3*) state for various JNI bindings. This state is + allocated as needed, cleaned up in sqlite3_close(_v2)(), and + recycled when possible. It is freed during sqlite3_shutdown(). */ typedef struct PerDbStateJni PerDbStateJni; struct PerDbStateJni { JNIEnv *env /* The associated JNIEnv handle */; - sqlite3 *pDb /* The associated db handle */; - jobject jDb /* a global ref of the object which was passed to - sqlite3_open(_v2)(). We need this in order to have an - object to pass to sqlite3_collation_needed()'s - callback, or else we have to dynamically create one - for that purpose, which would be fine except that it - would be a different instance (and maybe even a - different class) than the one the user expects to - receive. */; + sqlite3 *pDb /* The associated db handle */; + jobject jDb /* A global ref of the object which was passed to + sqlite3_open(_v2)(). We need this in order to have + an object to pass to sqlite3_collation_needed()'s + callback, or else we have to dynamically create one + for that purpose, which would be fine except that + it would be a different instance (and maybe even a + different class) than the one the user may expect + to receive. */; PerDbStateJni * pNext /* Next entry in the available/free list */; PerDbStateJni * pPrev /* Previous entry in the available/free list */; JniHookState busyHandler; @@ -2369,6 +2362,16 @@ JDECL(jbyteArray,1value_1text16be)(JENV_JSELF, jobject jpSVal){ return value_text16(SQLITE_UTF16BE, env, jpSVal); } +JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ + MARKER(("\nVarious bits of internal info:\n")); +#define SO(T) printf("sizeof(" #T ") = %u\n", (unsigned)sizeof(T)) + SO(void*); + SO(JniHookState); + SO(PerDbStateJni); + SO(S3Global); + SO(JNIEnvCache); +#undef SO +} //////////////////////////////////////////////////////////////////////// diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 07d2524be0..ee34f79fa3 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1603,6 +1603,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1value_1subtype JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1shutdown (JNIEnv *, jclass); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_do_something_for_developer + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1for_1developer + (JNIEnv *, jclass); + #ifdef __cplusplus } #endif diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index b41d011ea3..306a5fe1f6 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -779,6 +779,13 @@ public final class SQLite3Jni { public static native int sqlite3_shutdown(); + /** + This is NOT part of the public API. It exists solely as a place + to hook in arbitrary C-side code during development and testing + of this library. + */ + public static native void sqlite3_do_something_for_developer(); + ////////////////////////////////////////////////////////////////////// // SQLITE_... constants follow... diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 30b136d474..71d1452e46 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -929,7 +929,8 @@ public class Tester1 { testUpdateHook(); //testSleep(); if(liArgs.indexOf("-v")>0){ - listBoundMethods(); + sqlite3_do_something_for_developer(); + //listBoundMethods(); } final long timeEnd = System.nanoTime(); outln("Tests done. Metrics:"); diff --git a/manifest b/manifest index 9324f7b684..3437249371 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactor\sthe\sbusy-handler-specific\sJNI\shook\stype\sto\suse\sthe\sgeneric\shook\stype. -D 2023-07-31T10:08:36.744 +C Update\ssome\sinternal\sdocs\sfor\sthe\spast\stwo\scheckins.\sAdd\sa\sway\sto\sdump\sout\ssome\sdebug\sinfo\sabout\sthe\scurrent\sJNI\senvironment. +D 2023-07-31T10:22:34.406 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,10 +230,10 @@ 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/GNUmakefile 72a1549aa5ef6fd21b7af58baccd512147d0912ec85963da46c3aa011b6f3450 F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 9dc18b6eec43132aa9a5001bc12ddd29c69513a4c4d04717b4131a16b6782906 -F ext/jni/src/c/sqlite3-jni.h 28def286ee305c1c89a43ac5918a6862d985d0534f7ccbbd74df4885d3918b73 +F ext/jni/src/c/sqlite3-jni.c 2dd0c3c6d194f0283233da76ac4b62d877d5bc2bdbbd428d26ff3d481ef93bd7 +F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -243,8 +243,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 829a6409d6fa5e82b39d85df5f14643c93dff383978ccbf35dc4af3ba29b6e62 -F ext/jni/src/org/sqlite/jni/Tester1.java 1690172fccafbf8d8170b55b950003db182265c26dbb5a510122ec46a44d2611 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java dfc1cf977c3c56e2826a7c0f3050b2a9af12a05c2b6cad0a968c7f8d2efa4ced +F ext/jni/src/org/sqlite/jni/Tester1.java 0ef7c15ff5f9bbed4069c46c4f555023f6b280ac57ba71fb463caeaa473a8611 F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 02c1d3b6501fedf3d6e6d1ca60699df268522182c5ba3b49ae8f4691499ef0fc -R 5b96073cb268292054640619b4e4117b +P d9efdc6dd20a34bfdaad5d4bf8e67cce7e35238299eb91e4459d59fda11978a6 +R 34310ff83ce4e2977491819bd2bf34a0 U stephan -Z 2c77c8dd9c75c9f80fb562e45a42818f +Z 868da2f09fceab4d77e542bbc096ca63 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8ffd0dc238..683c6a5ee3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d9efdc6dd20a34bfdaad5d4bf8e67cce7e35238299eb91e4459d59fda11978a6 \ No newline at end of file +ac9b8bb1e64450d980e2986084996549ae5c59e68c9f0c4c69539c239b64468b \ No newline at end of file From ede89009122e79a76419592a780ea44bade71d85 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 10:42:05 +0000 Subject: [PATCH 034/148] Add another test for JNI-bound scalar UDFs. FossilOrigin-Name: 8b322d92e247be606f83977767dc361ee4f7bc819122a630bdaa1110177db9b8 --- ext/jni/src/c/sqlite3-jni.c | 4 +++- ext/jni/src/org/sqlite/jni/Tester1.java | 11 ++++++++++- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index f632cd6aef..fcf1f4111d 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -453,6 +453,7 @@ static void s3jni_call_xDestroy(JNIEnv *env, jobject jObj, jclass klazz){ assert(klazz); } method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); if(method){ (*env)->CallVoidMethod(env, jObj, method); IFTHREW{ @@ -1090,7 +1091,7 @@ static UDFState * UDFState_alloc(JNIEnv *env, jobject jObj){ static void UDFState_free(UDFState * s){ JNIEnv * const env = s->env; if(env){ - //MARKER(("UDF cleanup...\n")); + //MARKER(("UDF cleanup: %s\n", s->zFuncName)); s3jni_call_xDestroy(env, s->jObj, s->klazz); UNREF_G(s->jObj); UNREF_G(s->klazz); @@ -1100,6 +1101,7 @@ static void UDFState_free(UDFState * s){ } static void UDFState_finalizer(void * s){ + //MARKER(("UDF finalizer @ %p\n", s)); if(s) UDFState_free((UDFState*)s); } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 71d1452e46..62e6106bfd 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -474,7 +474,16 @@ public class Tester1 { int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); affirm(0 == rc); affirm(0 == xFuncAccum.value); - execSql(db, "SELECT myfunc(1,2,3)"); + final sqlite3_stmt stmt = new sqlite3_stmt(); + rc = sqlite3_prepare(db, "SELECT myfunc(1,2,3)", stmt); + affirm( 0==rc ); + int n = 0; + while( SQLITE_ROW == sqlite3_step(stmt) ){ + affirm( 6 == sqlite3_column_int(stmt, 0) ); + ++n; + } + sqlite3_finalize(stmt); + affirm(1 == n); affirm(6 == xFuncAccum.value); affirm( !xDestroyCalled.value ); sqlite3_close_v2(db); diff --git a/manifest b/manifest index 3437249371..b88820e598 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\ssome\sinternal\sdocs\sfor\sthe\spast\stwo\scheckins.\sAdd\sa\sway\sto\sdump\sout\ssome\sdebug\sinfo\sabout\sthe\scurrent\sJNI\senvironment. -D 2023-07-31T10:22:34.406 +C Add\sanother\stest\sfor\sJNI-bound\sscalar\sUDFs. +D 2023-07-31T10:42:05.281 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 72a1549aa5ef6fd21b7af58baccd512147d0912ec85963da46c3aa011b6f3450 F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 2dd0c3c6d194f0283233da76ac4b62d877d5bc2bdbbd428d26ff3d481ef93bd7 +F ext/jni/src/c/sqlite3-jni.c c06cab707e15221f928b5a791f7b19297cc10bfa20a203d8e8b176ea57705d99 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -244,7 +244,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java dfc1cf977c3c56e2826a7c0f3050b2a9af12a05c2b6cad0a968c7f8d2efa4ced -F ext/jni/src/org/sqlite/jni/Tester1.java 0ef7c15ff5f9bbed4069c46c4f555023f6b280ac57ba71fb463caeaa473a8611 +F ext/jni/src/org/sqlite/jni/Tester1.java 2c74f1e2411c717a53c2fde601fe4e4ac199e7a54cd5f3eabe968e13e4944abe F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d9efdc6dd20a34bfdaad5d4bf8e67cce7e35238299eb91e4459d59fda11978a6 -R 34310ff83ce4e2977491819bd2bf34a0 +P ac9b8bb1e64450d980e2986084996549ae5c59e68c9f0c4c69539c239b64468b +R 952a9335987fe05102caea2cebc40171 U stephan -Z 868da2f09fceab4d77e542bbc096ca63 +Z 0e30098b30d6010b9c0a3cea36325cf2 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 683c6a5ee3..e341e876a2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ac9b8bb1e64450d980e2986084996549ae5c59e68c9f0c4c69539c239b64468b \ No newline at end of file +8b322d92e247be606f83977767dc361ee4f7bc819122a630bdaa1110177db9b8 \ No newline at end of file From 8f714e21a02d60c9f699bde5b9fc4da7787923bd Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 10:55:30 +0000 Subject: [PATCH 035/148] Correct a mis-cast in the JNI wrapper which just happened to accidentally work. Update JNI binding of sqlite3_context_db_handle() to return the bound-at-open() db instance instead of a new/temp proxy object. FossilOrigin-Name: 9faca5d9ed4a749421e08bd1da8b7672c0fd31366124fdb613c46e19dece0fc1 --- ext/jni/src/c/sqlite3-jni.c | 28 ++++++++++++++-------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 4 ++++ ext/jni/src/org/sqlite/jni/Tester1.java | 3 +-- manifest | 16 ++++++------- manifest.uuid | 2 +- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index fcf1f4111d..0dcda80e1a 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -748,18 +748,29 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ true then a new instance will be allocated if no mapping currently exists, else NULL is returned if no mapping is found. - The 3rd and 4th args should only be non-0 for - sqlite3_open(_v2)(). For all other cases, they must be 0, in which + The 3rd and 4th args should normally only be non-0 for + sqlite3_open(_v2)(). For most other cases, they must be 0, in which case the db handle will be fished out of the jDb object and NULL is returned if jDb does not have any associated PerDbStateJni. + + If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST + be false and it will look for a matching db object. That usage is + required for functions, like sqlite3_context_db_handle(), which + return a (sqlite3*). */ FIXME_THREADING static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ PerDbStateJni * s = S3Global.perDb.aUsed; - if(!jDb) return 0; + if(!jDb){ + if(pDb){ + assert(!allocIfNeeded); + }else{ + return 0; + } + } assert(allocIfNeeded ? !!pDb : 1); if(!allocIfNeeded && !pDb){ - pDb = PtrGet_sqlite3_value(jDb); + pDb = PtrGet_sqlite3(jDb); } for( ; pDb && s; s = s->pNext){ if(s->pDb == pDb) return s; @@ -1024,10 +1035,6 @@ static jobject new_sqlite3_context_wrapper(JNIEnv *env, sqlite3_context *sv){ return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_context", sv); } -static jobject new_sqlite3_wrapper(JNIEnv *env, sqlite3 *sv){ - return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3", sv); -} - enum UDFType { UDF_SCALAR = 1, UDF_AGGREGATE, @@ -1670,8 +1677,9 @@ JDECL(jboolean,1compileoption_1used)(JENV_JSELF, jstring name){ } JDECL(jobject,1context_1db_1handle)(JENV_JSELF, jobject jpCx){ - sqlite3 * const db = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); - return db ? new_sqlite3_wrapper(env, db) : NULL; + sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); + PerDbStateJni * const ps = pDb ? PerDbStateJni_for_db(env, 0, pDb, 0) : 0; + return ps ? ps->jDb : 0; } JDECL(jint,1create_1collation)(JENV_JSELF, jobject jDb, diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 306a5fe1f6..628aa86bfb 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -271,6 +271,10 @@ public final class SQLite3Jni { public static native int sqlite3_collation_needed(@NotNull sqlite3 db, @Nullable CollationNeeded callback); + /** + Returns the db handle passed to sqlite3_open() or + sqlite3_open_v2(), as opposed to a new wrapper object. + */ public static native sqlite3 sqlite3_context_db_handle(@NotNull sqlite3_context cx); public static native CommitHook sqlite3_commit_hook(@NotNull sqlite3 db, @Nullable CommitHook hook); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 62e6106bfd..c12245f68b 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -457,8 +457,7 @@ public class Tester1 { // Java... new SQLFunction.Scalar(){ public void xFunc(sqlite3_context cx, sqlite3_value[] args){ - affirm(db.getNativePointer() - == sqlite3_context_db_handle(cx).getNativePointer()); + affirm(db == sqlite3_context_db_handle(cx)); int result = 0; for( sqlite3_value v : args ) result += sqlite3_value_int(v); xFuncAccum.value += result;// just for post-run testing diff --git a/manifest b/manifest index b88820e598..748cff4822 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sanother\stest\sfor\sJNI-bound\sscalar\sUDFs. -D 2023-07-31T10:42:05.281 +C Correct\sa\smis-cast\sin\sthe\sJNI\swrapper\swhich\sjust\shappened\sto\saccidentally\swork.\sUpdate\sJNI\sbinding\sof\ssqlite3_context_db_handle()\sto\sreturn\sthe\sbound-at-open()\sdb\sinstance\sinstead\sof\sa\snew/temp\sproxy\sobject. +D 2023-07-31T10:55:30.156 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 72a1549aa5ef6fd21b7af58baccd512147d0912ec85963da46c3aa011b6f3450 F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c c06cab707e15221f928b5a791f7b19297cc10bfa20a203d8e8b176ea57705d99 +F ext/jni/src/c/sqlite3-jni.c b552ef9c4ec60d4ea29815d5b2cf7e64b20e100a7eb2741c5382e17b0e49999f F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -243,8 +243,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java dfc1cf977c3c56e2826a7c0f3050b2a9af12a05c2b6cad0a968c7f8d2efa4ced -F ext/jni/src/org/sqlite/jni/Tester1.java 2c74f1e2411c717a53c2fde601fe4e4ac199e7a54cd5f3eabe968e13e4944abe +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java b522c6456ab66026af5c487e4ac40410be36374d0550c2a03ea28421c4d39029 +F ext/jni/src/org/sqlite/jni/Tester1.java 13ca6badf8e79c39fe52c00306f55a1f55b6d504d8991132eca842e08eb2dc1e F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ac9b8bb1e64450d980e2986084996549ae5c59e68c9f0c4c69539c239b64468b -R 952a9335987fe05102caea2cebc40171 +P 8b322d92e247be606f83977767dc361ee4f7bc819122a630bdaa1110177db9b8 +R 42363264995216c484a93a93750ae3b7 U stephan -Z 0e30098b30d6010b9c0a3cea36325cf2 +Z e56a10315f9f662ddf790f3b196580e4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e341e876a2..63395cf23e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8b322d92e247be606f83977767dc361ee4f7bc819122a630bdaa1110177db9b8 \ No newline at end of file +9faca5d9ed4a749421e08bd1da8b7672c0fd31366124fdb613c46e19dece0fc1 \ No newline at end of file From e3e9bdcd0ba06f49ab86a50f0010ca6022ca2231 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 12:10:32 +0000 Subject: [PATCH 036/148] Add some JNI-internal metrics, accessible via passing -v when running Tester1.java. Document an OpenJDK bug which leads to incorrect -Xlint:jni warnings. FossilOrigin-Name: a5d68a6b64abe3c2dfc3a32157f70fd8a4ad89feef2510b3bbb2d86b325d51ae --- ext/jni/GNUmakefile | 1 + ext/jni/src/c/sqlite3-jni.c | 85 ++++++++++++++++++++++++++++++++----- manifest | 14 +++--- manifest.uuid | 2 +- 4 files changed, 83 insertions(+), 19 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index b313775f2b..424709d413 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -145,6 +145,7 @@ $(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) $(sqlite3-jni.c) -shared -o $@ all: $(sqlite3-jni.dll) +test.flags ?= -v test: $(SQLite3Jni.class) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 0dcda80e1a..088ae4f88b 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -13,6 +13,17 @@ ** org.sqlite.jni.SQLiteJni (from which sqlite3-jni.h is generated). */ +/** + If you found this comment by searching the code for + CallStaticObjectMethod then you're the victim of an OpenJDK bug: + + https://bugs.openjdk.org/browse/JDK-8130659 + + It's known to happen with OpenJDK v8 but not with v19. + + This code does not use JNI's CallStaticObjectMethod(). +*/ + /* ** Define any SQLITE_... config defaults we want if they aren't ** overridden by the builder. Please keep these alphabetized. @@ -158,6 +169,10 @@ (*env)->ExceptionDescribe(env) #define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT #define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR +#define EXCEPTION_CANNOT_HAPPEN IFTHREW{\ + EXCEPTION_REPORT; \ + (*env)->FatalError(env,"This \"cannot happen\"."); \ + } /** Helpers for extracting pointers from jobjects, noting that the @@ -391,6 +406,20 @@ static struct { PerDbStateJni * aUsed; PerDbStateJni * aFree; } perDb; + struct { + unsigned nphCacheHits; + unsigned nphCacheMisses; + unsigned envCacheHits; + unsigned envCacheMisses; + unsigned nDestroy; + struct { + unsigned nFunc; + unsigned nStep; + unsigned nFinal; + unsigned nValue; + unsigned nInverse; + } udf; + } metrics; } S3Global; /** @@ -455,6 +484,7 @@ static void s3jni_call_xDestroy(JNIEnv *env, jobject jObj, jclass klazz){ method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); if(method){ + ++S3Global.metrics.nDestroy; (*env)->CallVoidMethod(env, jObj, method); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW; @@ -472,7 +502,6 @@ static void s3jni_call_xDestroy(JNIEnv *env, jobject jObj, jclass klazz){ insofar as possible. Calls (*env)->FatalError() if the cache fills up. That's hypothetically possible but "shouldn't happen." If it does, we can dynamically allocate these instead. - */ FIXME_THREADING static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ @@ -480,9 +509,13 @@ static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ int i = 0; for( ; i < JNIEnvCache_SIZE; ++i ){ row = &S3Global.envCache.lines[i]; - if(row->env == env) return row; + if(row->env == env){ + ++S3Global.metrics.envCacheHits; + return row; + } else if(!row->env) break; } + ++S3Global.metrics.envCacheMisses; if(i == JNIEnvCache_SIZE){ (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full."); return NULL; @@ -510,8 +543,8 @@ static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ zClassName must be a static string so we can use its address as a cache key. - This simple cache catches the overwhelming majority of searches - (>95%) in the current (2023-07-24) tests. + This simple cache catches >99% of searches in the current + (2023-07-31) tests. */ FIXME_THREADING static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassName){ @@ -538,11 +571,11 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN for( i = 0; i < NphCache_SIZE; ++i ){ cacheLine = &envRow->nph[i]; if(zClassName == cacheLine->zClassName){ + ++S3Global.metrics.nphCacheHits; #define DUMP_NPH_CACHES 0 #if DUMP_NPH_CACHES - static unsigned int n = 0; MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - ++n, zClassName, cacheLine->klazz, cacheLine->fidValue, + S3Global.metrics.nphCacheHits, zClassName, cacheLine->klazz, cacheLine->fidValue, cacheLine->midCtor)); #endif assert(cacheLine->klazz); @@ -554,10 +587,12 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN if(freeSlot){ freeSlot->zClassName = zClassName; freeSlot->klazz = REF_G((*env)->FindClass(env, zClassName)); + EXCEPTION_CANNOT_HAPPEN; + ++S3Global.metrics.nphCacheMisses; #if DUMP_NPH_CACHES static unsigned int cacheMisses = 0; MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - ++cacheMisses, zClassName, freeSlot->klazz, + S3Global.metrics.nphCacheMisses, zClassName, freeSlot->klazz, freeSlot->fidValue, freeSlot->midCtor)); #endif #undef DUMP_NPH_CACHES @@ -756,7 +791,7 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST be false and it will look for a matching db object. That usage is required for functions, like sqlite3_context_db_handle(), which - return a (sqlite3*). + return a (sqlite3*) but do not take one as an argument. */ FIXME_THREADING static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ @@ -1161,8 +1196,7 @@ static int udf_report_exception(sqlite3_context * cx, UDFState *s, const char *zFuncType){ int rc; char * z = - sqlite3_mprintf("UDF %s.%s() threw. FIXME: extract " - "Java-side exception message.", + sqlite3_mprintf("UDF %s.%s() threw. It should not do that.", s->zFuncName, zFuncType); if(z){ sqlite3_result_error(cx, z, -1); @@ -1174,6 +1208,10 @@ static int udf_report_exception(sqlite3_context * cx, UDFState *s, return rc; } +/** + Sets up the state for calling a Java-side xFunc/xStep/xInverse() + UDF, calls it, and returns 0 on success. +*/ static int udf_xFSI(sqlite3_context* pCx, int argc, sqlite3_value** argv, UDFState * s, @@ -1199,6 +1237,10 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, return rc; } +/** + Sets up the state for calling a Java-side xFinal/xValue() UDF, + calls it, and returns 0 on success. +*/ static int udf_xFV(sqlite3_context* cx, UDFState * s, jmethodID xMethodID, const char *zFuncType){ @@ -1227,24 +1269,29 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s, static void udf_xFunc(sqlite3_context* cx, int argc, sqlite3_value** argv){ UDFState * const s = (UDFState*)sqlite3_user_data(cx); + ++S3Global.metrics.udf.nFunc; udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); } static void udf_xStep(sqlite3_context* cx, int argc, sqlite3_value** argv){ UDFState * const s = (UDFState*)sqlite3_user_data(cx); + ++S3Global.metrics.udf.nStep; udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); } static void udf_xFinal(sqlite3_context* cx){ UDFState * const s = (UDFState*)sqlite3_user_data(cx); + ++S3Global.metrics.udf.nFinal; udf_xFV(cx, s, s->jmidxFinal, "xFinal"); } static void udf_xValue(sqlite3_context* cx){ UDFState * const s = (UDFState*)sqlite3_user_data(cx); + ++S3Global.metrics.udf.nValue; udf_xFV(cx, s, s->jmidxValue, "xValue"); } static void udf_xInverse(sqlite3_context* cx, int argc, sqlite3_value** argv){ UDFState * const s = (UDFState*)sqlite3_user_data(cx); + ++S3Global.metrics.udf.nInverse; udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); } @@ -2374,12 +2421,26 @@ JDECL(jbyteArray,1value_1text16be)(JENV_JSELF, jobject jpSVal){ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ MARKER(("\nVarious bits of internal info:\n")); -#define SO(T) printf("sizeof(" #T ") = %u\n", (unsigned)sizeof(T)) + puts("sizeofs:"); +#define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); SO(JniHookState); SO(PerDbStateJni); SO(S3Global); SO(JNIEnvCache); + printf("Cache info:\n"); + printf("\tNativePointerHolder cache: %u misses, %u hits\n", + S3Global.metrics.nphCacheMisses, + S3Global.metrics.nphCacheHits); + printf("\tJNIEnv cache %u misses, %u hits\n", + S3Global.metrics.envCacheMisses, + S3Global.metrics.envCacheHits); + puts("UDF calls:"); +#define UDF(T) printf("\t%-8s = %u\n", #T, S3Global.metrics.udf.n##T) + UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); +#undef UDF + printf("xDestroy calls across all callback types: %u\n", + S3Global.metrics.nDestroy); #undef SO } @@ -2443,8 +2504,10 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv *env, jclass self, jobject sJni){ for( pLimit = &aLimits[0]; pLimit->zName; ++pLimit ){ fieldId = (*env)->GetStaticFieldID(env, klazz, pLimit->zName, "I"); + EXCEPTION_CANNOT_HAPPEN; //MARKER(("Setting %s (field=%p) = %d\n", pLimit->zName, fieldId, pLimit->value)); assert(fieldId); (*env)->SetStaticIntField(env, klazz, fieldId, (jint)pLimit->value); + EXCEPTION_CANNOT_HAPPEN; } } diff --git a/manifest b/manifest index 748cff4822..82ad29b065 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\sa\smis-cast\sin\sthe\sJNI\swrapper\swhich\sjust\shappened\sto\saccidentally\swork.\sUpdate\sJNI\sbinding\sof\ssqlite3_context_db_handle()\sto\sreturn\sthe\sbound-at-open()\sdb\sinstance\sinstead\sof\sa\snew/temp\sproxy\sobject. -D 2023-07-31T10:55:30.156 +C Add\ssome\sJNI-internal\smetrics,\saccessible\svia\spassing\s-v\swhen\srunning\sTester1.java.\sDocument\san\sOpenJDK\sbug\swhich\sleads\sto\sincorrect\s-Xlint:jni\swarnings. +D 2023-07-31T12:10:32.812 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,9 +230,9 @@ 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 72a1549aa5ef6fd21b7af58baccd512147d0912ec85963da46c3aa011b6f3450 +F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c b552ef9c4ec60d4ea29815d5b2cf7e64b20e100a7eb2741c5382e17b0e49999f +F ext/jni/src/c/sqlite3-jni.c 7be564e523b576b130d930ed7ad3b389cf22372689101766c3a032166f4438a5 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 8b322d92e247be606f83977767dc361ee4f7bc819122a630bdaa1110177db9b8 -R 42363264995216c484a93a93750ae3b7 +P 9faca5d9ed4a749421e08bd1da8b7672c0fd31366124fdb613c46e19dece0fc1 +R 2f7ebe9a2d19552b91a45c55eec1bac7 U stephan -Z e56a10315f9f662ddf790f3b196580e4 +Z 7f375306dd5a29931e39a5081c815b0c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 63395cf23e..a908ec37fd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9faca5d9ed4a749421e08bd1da8b7672c0fd31366124fdb613c46e19dece0fc1 \ No newline at end of file +a5d68a6b64abe3c2dfc3a32157f70fd8a4ad89feef2510b3bbb2d86b325d51ae \ No newline at end of file From db6f0bef9133f012e8a41ab11406cd1d7d819202 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 31 Jul 2023 13:52:46 +0000 Subject: [PATCH 037/148] Experimentally change the JNI sqlite3_trace_v2() callback type to have more convenient access to the current Java-side sqlite3_stmt at the cost of some uncomfortably fiddly current-statement tracking in the JNI layer. Subject to change. FossilOrigin-Name: 459db332af6ea358b42bac096b9d26f1045b9ec32fad8463bca06807b2396b2c --- ext/jni/src/c/sqlite3-jni.c | 156 ++++++++++++++++++------ ext/jni/src/org/sqlite/jni/Tester1.java | 31 +++-- ext/jni/src/org/sqlite/jni/Tracer.java | 14 ++- manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 155 insertions(+), 64 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 088ae4f88b..ee389d4032 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -164,9 +164,11 @@ #define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) #define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) -#define EXCEPTION_WARN_CALLBACK_THREW \ - MARKER(("WARNING: this routine MUST NOT THROW.\n")); \ +#define EXCEPTION_WARN_CALLBACK_THREW1(STR) \ + MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ (*env)->ExceptionDescribe(env) +#define EXCEPTION_WARN_CALLBACK_THREW \ + EXCEPTION_WARN_CALLBACK_THREW1("this routine") #define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT #define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR #define EXCEPTION_CANNOT_HAPPEN IFTHREW{\ @@ -313,6 +315,18 @@ struct JNIEnvCacheLine { jclass globalClassObj /* global ref to java.lang.Object */; jclass globalClassLong /* global ref to java.lang.Long */; jmethodID ctorLong1 /* the Long(long) constructor */; + jobject currentStmt /* Current Java sqlite3_stmt object being + prepared, stepped, reset, or + finalized. Needed for tracing, the + alternative being that we create a new + sqlite3_stmt wrapper object for every + tracing call which needs a stmt + object. This approach is rather invasive, + however, and there may well be places + tracing may trigger which we have no + accounted for, so it may be best to + redefine the tracing API rather than + passing through the statement handles. */; struct NphCacheLine nph[NphCache_SIZE]; }; typedef struct JNIEnvCache JNIEnvCache; @@ -1070,6 +1084,10 @@ static jobject new_sqlite3_context_wrapper(JNIEnv *env, sqlite3_context *sv){ return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_context", sv); } +static jobject new_sqlite3_stmt_wrapper(JNIEnv *env, sqlite3_stmt *sv){ + return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_stmt", sv); +} + enum UDFType { UDF_SCALAR = 1, UDF_AGGREGATE, @@ -1320,10 +1338,8 @@ WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) WRAP_INT_STMT(1data_1count, sqlite3_data_count) WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) -WRAP_INT_STMT(1reset, sqlite3_reset) WRAP_INT_INT(1sleep, sqlite3_sleep) WRAP_MUTF8_VOID(1sourceid, sqlite3_sourceid) -WRAP_INT_STMT(1step, sqlite3_step) WRAP_INT_VOID(1threadsafe, sqlite3_threadsafe) WRAP_INT_DB(1total_1changes, sqlite3_total_changes) WRAP_INT64_DB(1total_1changes64, sqlite3_total_changes64) @@ -1846,13 +1862,27 @@ JDECL(jint,1initialize)(JENV_JSELF){ return sqlite3_initialize(); } +/** + Sets jc->currentStmt to the 2nd arugment and returns the previous value + of jc->currentStmt. +*/ +static jobject stmt_set_current(JNIEnvCacheLine * const jc, jobject jStmt){ + jobject const old = jc->currentStmt; + jc->currentStmt = jStmt; + return old; +} + JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ - if( jpStmt ){ - sqlite3_stmt * pStmt = PtrGet_sqlite3_stmt(jpStmt); + int rc = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + jobject const pPrev = stmt_set_current(jc, jpStmt); + rc = sqlite3_finalize(pStmt); setNativePointer(env, jpStmt, 0, ClassNames.sqlite3_stmt); - if( pStmt ) sqlite3_finalize(pStmt); + (void)stmt_set_current(jc, pPrev); } - return 0; + return rc; } @@ -1908,10 +1938,12 @@ JDECL(jint,1open_1v2)(JENV_JSELF, jstring strName, static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, jint nMax, jint prepFlags, - jobject outStmt, jobject outTail){ + jobject jOutStmt, jobject outTail){ sqlite3_stmt * pStmt = 0; const char * zTail = 0; jbyte * const pBuf = JBA_TOC(baSql); + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + jobject const pOldStmt = stmt_set_current(jc, jOutStmt); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); switch( prepVersion ){ @@ -1934,23 +1966,24 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv *env, jclass self, assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); setOutputInt32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); } - setNativePointer(env, outStmt, pStmt, ClassNames.sqlite3_stmt); + setNativePointer(env, jOutStmt, pStmt, ClassNames.sqlite3_stmt); + (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } JDECL(jint,1prepare)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, - jint nMax, jobject outStmt, jobject outTail){ + jint nMax, jobject jOutStmt, jobject outTail){ return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0, - outStmt, outTail); + jOutStmt, outTail); } JDECL(jint,1prepare_1v2)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, - jint nMax, jobject outStmt, jobject outTail){ + jint nMax, jobject jOutStmt, jobject outTail){ return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0, - outStmt, outTail); + jOutStmt, outTail); } JDECL(jint,1prepare_1v3)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, - jint nMax, jint prepFlags, jobject outStmt, jobject outTail){ + jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax, - prepFlags, outStmt, outTail); + prepFlags, jOutStmt, outTail); } @@ -1999,6 +2032,18 @@ JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress } +JDECL(jint,1reset)(JENV_JSELF, jobject jpStmt){ + int rc = 0; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + if( pStmt ){ + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + jobject const pPrev = stmt_set_current(jc, jpStmt); + rc = sqlite3_reset(pStmt); + (void)stmt_set_current(jc, pPrev); + } + return rc; +} + /* sqlite3_result_text/blob() and friends. */ static void result_blob_text(int asBlob, int as64, int eTextRep/*only for (asBlob=0)*/, @@ -2191,38 +2236,71 @@ JDECL(jint,1shutdown)(JENV_JSELF){ return sqlite3_shutdown(); } +JDECL(jint,1step)(JENV_JSELF,jobject jStmt){ + jobject jPrevStmt = 0; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + int rc; + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); + if(!pStmt) return SQLITE_MISUSE; + jPrevStmt = stmt_set_current(jc, jStmt); + rc = sqlite3_step(pStmt); + (void)stmt_set_current(jc, jPrevStmt); + return rc; +} + static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ PerDbStateJni * const ps = (PerDbStateJni *)pC; JNIEnv * const env = ps->env; - jobject jX = NULL; - JNIEnvCacheLine * const pEcl = S3Global_env_cache(env); + jobject jX = NULL /* the tracer's X arg */; + jobject jP = NULL /* the tracer's P arg */; + jobject jPUnref = NULL /* potentially a local ref to jP */; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); int rc; - /** - TODO: convert pX depending on traceflag: - - SQLITE_TRACE_STMT: String - SQLITE_TRACE_PROFILE: Long - others: null - */ switch(traceflag){ - case SQLITE_TRACE_STMT: - /* This is not _quite_ right: we're converting to MUTF-8. It - should(?) suffice for purposes of tracing, though. */ - jX = (*env)->NewStringUTF(env, (const char *)pX); - break; - case SQLITE_TRACE_PROFILE: - jX = (*env)->NewObject(env, pEcl->globalClassLong, pEcl->ctorLong1, - (jlong)*((sqlite3_int64*)pX)); - break; + case SQLITE_TRACE_STMT: + /* This is not _quite_ right: we're converting to MUTF-8. It + should(?) suffice for purposes of tracing, though. */ + jX = (*env)->NewStringUTF(env, (const char *)pX); + if(!jX) return SQLITE_NOMEM; + jP = jc->currentStmt; + break; + case SQLITE_TRACE_PROFILE: + jX = (*env)->NewObject(env, jc->globalClassLong, jc->ctorLong1, + (jlong)*((sqlite3_int64*)pX)); + // hmm. It really is zero. + // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); + jP = jc->currentStmt; + if(!jP){ + jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); + if(!jP){ + UNREF_L(jX); + return SQLITE_NOMEM; + } + MARKER(("WARNING: created new sqlite3_stmt wrapper for TRACE_PROFILE. stmt@%p\n" + "This means we have missed a route into the tracing API and it " + "needs the stmt_set_current() treatment littered around a handful " + "of other functions.\n", pP)); + } + break; + case SQLITE_TRACE_ROW: + jP = jc->currentStmt; + break; + case SQLITE_TRACE_CLOSE: + jP = ps->jDb; + break; + default: + assert(!"cannot happen - unkown trace flag"); + return SQLITE_ERROR; } + assert(jP); rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, ps->trace.midCallback, - (jint)traceflag, (jlong)pP, jX); + (jint)traceflag, jP, jX); + UNREF_L(jPUnref); UNREF_L(jX); IFTHREW{ - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, - "sqlite3_trace_v2() callback threw."); + EXCEPTION_WARN_CALLBACK_THREW1("sqlite3_trace_v2() callback"); + rc = SQLITE_ERROR; } return rc; } @@ -2240,7 +2318,7 @@ JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ if(!ps) return SQLITE_NOMEM; klazz = (*env)->GetObjectClass(env, jTracer); ps->trace.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", - "(IJLjava/lang/Object;)I"); + "(ILjava/lang/Object;Ljava/lang/Object;)I"); IFTHREW { EXCEPTION_CLEAR; return s3jni_db_error(ps->pDb, SQLITE_ERROR, diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index c12245f68b..23bd038348 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -644,7 +644,6 @@ public class Tester1 { } private static void listBoundMethods(){ - //public static List getStatics(Class clazz) { if(false){ final java.lang.reflect.Field[] declaredFields = SQLite3Jni.class.getDeclaredFields(); @@ -682,26 +681,36 @@ public class Tester1 { db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, new Tracer(){ - public int xCallback(int traceFlag, long pNative, Object x){ + public int xCallback(int traceFlag, Object pNative, Object x){ ++counter.value; - //outln("Trace #"+counter.value+" flag="+traceFlag+": "+x); switch(traceFlag){ case SQLITE_TRACE_STMT: - // pNative ==> sqlite3_stmt - affirm(x instanceof String); break; + affirm(pNative instanceof sqlite3_stmt); + affirm(x instanceof String); + //outln("TRACE_STMT sql = "+x); + break; case SQLITE_TRACE_PROFILE: - // pNative ==> sqlite3_stmt - affirm(x instanceof Long); break; + affirm(pNative instanceof sqlite3_stmt); + affirm(x instanceof Long); + //outln("TRACE_PROFILE time = "+x); + break; case SQLITE_TRACE_ROW: - // pNative ==> sqlite3_stmt - case SQLITE_TRACE_CLOSE: - // pNative ==> sqlite3 + affirm(pNative instanceof sqlite3_stmt); affirm(null == x); + //outln("TRACE_ROW = "+sqlite3_column_text((sqlite3_stmt)pNative, 0)); + break; + case SQLITE_TRACE_CLOSE: + affirm(pNative instanceof sqlite3); + affirm(null == x); + break; + default: + affirm(false /*cannot happen*/); + break; } return 0; } }); - execSql(db, "SELECT 1; SELECT 2"); + execSql(db, "SELECT coalesce(null,null,null,'hi'); SELECT 'world'"); affirm( 6 == counter.value ); sqlite3_close_v2(db); affirm( 7 == counter.value ); diff --git a/ext/jni/src/org/sqlite/jni/Tracer.java b/ext/jni/src/org/sqlite/jni/Tracer.java index 52ccfabafc..fa62edbfa7 100644 --- a/ext/jni/src/org/sqlite/jni/Tracer.java +++ b/ext/jni/src/org/sqlite/jni/Tracer.java @@ -18,6 +18,10 @@ package org.sqlite.jni; */ public interface Tracer { /** + Achtung: this interface is subject to change because the current + approach to mapping the passed-in natives back to Java is + uncomfortably quirky. + 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 @@ -37,10 +41,10 @@ public interface Tracer { 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 + containing the prepared SQL, with one caveat: 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 @@ -54,5 +58,5 @@ public interface Tracer { - SQLITE_TRACE_CLOSE: pNative is a sqlite3. pX is null. */ - int xCallback(int traceFlag, long pNative, Object pX); + int xCallback(int traceFlag, Object pNative, Object pX); } diff --git a/manifest b/manifest index 82ad29b065..dcd27fee56 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssome\sJNI-internal\smetrics,\saccessible\svia\spassing\s-v\swhen\srunning\sTester1.java.\sDocument\san\sOpenJDK\sbug\swhich\sleads\sto\sincorrect\s-Xlint:jni\swarnings. -D 2023-07-31T12:10:32.812 +C Experimentally\schange\sthe\sJNI\ssqlite3_trace_v2()\scallback\stype\sto\shave\smore\sconvenient\saccess\sto\sthe\scurrent\sJava-side\ssqlite3_stmt\sat\sthe\scost\sof\ssome\suncomfortably\sfiddly\scurrent-statement\stracking\sin\sthe\sJNI\slayer.\sSubject\sto\schange. +D 2023-07-31T13:52:46.522 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 7be564e523b576b130d930ed7ad3b389cf22372689101766c3a032166f4438a5 +F ext/jni/src/c/sqlite3-jni.c 4d680b84d7def871186e0787cbaa14347952c1c4ea05530440bb237a7a97886c F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -244,8 +244,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java b522c6456ab66026af5c487e4ac40410be36374d0550c2a03ea28421c4d39029 -F ext/jni/src/org/sqlite/jni/Tester1.java 13ca6badf8e79c39fe52c00306f55a1f55b6d504d8991132eca842e08eb2dc1e -F ext/jni/src/org/sqlite/jni/Tracer.java c2fe1eba4a76581b93b375a7b95ab1919e5ae60accfb06d6beb067b033e9bae1 +F ext/jni/src/org/sqlite/jni/Tester1.java ee7ad9a45a282b12a5c2c5ab5f6fdb14a398f854f29cdeef457c81cceeddad16 +F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 9faca5d9ed4a749421e08bd1da8b7672c0fd31366124fdb613c46e19dece0fc1 -R 2f7ebe9a2d19552b91a45c55eec1bac7 +P a5d68a6b64abe3c2dfc3a32157f70fd8a4ad89feef2510b3bbb2d86b325d51ae +R cb629a46215e7de4fde069b749d0908e U stephan -Z 7f375306dd5a29931e39a5081c815b0c +Z 8e74ea4537f0e72a50fc3ee99cfef378 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a908ec37fd..bd9b7db410 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a5d68a6b64abe3c2dfc3a32157f70fd8a4ad89feef2510b3bbb2d86b325d51ae \ No newline at end of file +459db332af6ea358b42bac096b9d26f1045b9ec32fad8463bca06807b2396b2c \ No newline at end of file From 0bc72d2b915d3f521c0a59b153efd58c04bcc14d Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 1 Aug 2023 08:48:13 +0000 Subject: [PATCH 038/148] Minor JNI-related doc tweaks and code re-ordering. FossilOrigin-Name: b663b27e425966f34fb62482a18048f0e2934380e5c411ae3627f1fe6a765c04 --- ext/jni/src/c/sqlite3-jni.c | 4 ++-- ext/jni/src/org/sqlite/jni/Tester1.java | 10 ++++------ manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index ee389d4032..e2993c31c8 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2296,12 +2296,12 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, ps->trace.midCallback, (jint)traceflag, jP, jX); - UNREF_L(jPUnref); - UNREF_L(jX); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW1("sqlite3_trace_v2() callback"); rc = SQLITE_ERROR; } + UNREF_L(jPUnref); + UNREF_L(jX); return rc; } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 23bd038348..69bf30756f 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -433,12 +433,12 @@ public class Tester1 { encoding while also claiming to export using RFC 2279: https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html + + Let's ensure that we can convert to standard UTF-8 in Java code + (noting that the JNI native API has no way to do this). */ final byte[] ba = "a \0 b".getBytes(StandardCharsets.UTF_8); - //out("\"a NUL b\" via getBytes(): "); affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); - //for( byte b : ba ) out( ""+b ); - //outln(""); } private static void testUdf1(){ @@ -452,9 +452,7 @@ public class Tester1 { 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... + // classes are a convenient way to implement these. new SQLFunction.Scalar(){ public void xFunc(sqlite3_context cx, sqlite3_value[] args){ affirm(db == sqlite3_context_db_handle(cx)); diff --git a/manifest b/manifest index dcd27fee56..161ddef685 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Experimentally\schange\sthe\sJNI\ssqlite3_trace_v2()\scallback\stype\sto\shave\smore\sconvenient\saccess\sto\sthe\scurrent\sJava-side\ssqlite3_stmt\sat\sthe\scost\sof\ssome\suncomfortably\sfiddly\scurrent-statement\stracking\sin\sthe\sJNI\slayer.\sSubject\sto\schange. -D 2023-07-31T13:52:46.522 +C Minor\sJNI-related\sdoc\stweaks\sand\scode\sre-ordering. +D 2023-08-01T08:48:13.434 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c 4d680b84d7def871186e0787cbaa14347952c1c4ea05530440bb237a7a97886c +F ext/jni/src/c/sqlite3-jni.c c67b9ae6ee3eda2c89523ccddc6fd98bc610c3ac1c55dfdca2971587ff4795ef F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -244,7 +244,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java b522c6456ab66026af5c487e4ac40410be36374d0550c2a03ea28421c4d39029 -F ext/jni/src/org/sqlite/jni/Tester1.java ee7ad9a45a282b12a5c2c5ab5f6fdb14a398f854f29cdeef457c81cceeddad16 +F ext/jni/src/org/sqlite/jni/Tester1.java 9443cdbd2b10f6a8e1f3abd1694983a16b17960f8ed2f7e06bcc7e535fb5abcf F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P a5d68a6b64abe3c2dfc3a32157f70fd8a4ad89feef2510b3bbb2d86b325d51ae -R cb629a46215e7de4fde069b749d0908e +P 459db332af6ea358b42bac096b9d26f1045b9ec32fad8463bca06807b2396b2c +R 2116d1859999fef41708d9f5ffc75a3f U stephan -Z 8e74ea4537f0e72a50fc3ee99cfef378 +Z e9112cb724b07771e50f3afa4c070a7b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bd9b7db410..1de4eef727 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -459db332af6ea358b42bac096b9d26f1045b9ec32fad8463bca06807b2396b2c \ No newline at end of file +b663b27e425966f34fb62482a18048f0e2934380e5c411ae3627f1fe6a765c04 \ No newline at end of file From e209d144c38cdfc807a19d40bf622ada3f4189cf Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 1 Aug 2023 09:44:26 +0000 Subject: [PATCH 039/148] Internal exception-handling cleanups in the JNI bindings. FossilOrigin-Name: 057b1d4f6ffff73c120566895f61ea3fd8118968464d67ec9262096a7aa03f39 --- ext/jni/README.md | 5 +- ext/jni/src/c/sqlite3-jni.c | 116 +++++++++++++++++++----------------- manifest | 14 ++--- manifest.uuid | 2 +- 4 files changed, 73 insertions(+), 64 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index b77f79a3f3..b668d61386 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -95,7 +95,10 @@ Known consequences and limitations of this discrepancy include: - Names of databases, tables, and collations must not contain characters which differ in MUTF-8 and UTF-8, or certain APIs will - mis-translate them on their way between languages. + mis-translate them on their way between languages. The + sqlite3_trace_v2() implementation is also currently affected by + this, in that it will necessarily translate traced SQL statements to + MUTF-8. [modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index e2993c31c8..f7322cea53 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -157,25 +157,25 @@ JNIEXPORT ReturnType JNICALL \ JFuncName(Suffix) /* First 2 parameters to all JNI bindings. */ -#define JENV_JSELF JNIEnv * env, jobject jSelf +#define JENV_JSELF JNIEnv * const env, jobject jSelf /* Helpers to squelch -Xcheck:jni warnings about not having checked for exceptions. */ #define IFTHREW if((*env)->ExceptionCheck(env)) #define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) #define EXCEPTION_REPORT (*env)->ExceptionDescribe(env) -#define EXCEPTION_WARN_CALLBACK_THREW1(STR) \ +#define EXCEPTION_WARN_CALLBACK_THREW(STR) \ MARKER(("WARNING: " STR " MUST NOT THROW.\n")); \ (*env)->ExceptionDescribe(env) -#define EXCEPTION_WARN_CALLBACK_THREW \ - EXCEPTION_WARN_CALLBACK_THREW1("this routine") #define IFTHREW_REPORT IFTHREW EXCEPTION_REPORT #define IFTHREW_CLEAR IFTHREW EXCEPTION_CLEAR -#define EXCEPTION_CANNOT_HAPPEN IFTHREW{\ - EXCEPTION_REPORT; \ - (*env)->FatalError(env,"This \"cannot happen\"."); \ - } +/** To be used for cases where we're _really_ not expecting an + exception, e.g. looking up well-defined Java class members. */ +#define EXCEPTION_IS_FATAL(MSG) IFTHREW {\ + EXCEPTION_REPORT; EXCEPTION_CLEAR; \ + (*env)->FatalError(env, MSG); \ + } /** Helpers for extracting pointers from jobjects, noting that the corresponding Java interfaces have already done the type-checking. @@ -327,6 +327,12 @@ struct JNIEnvCacheLine { accounted for, so it may be best to redefine the tracing API rather than passing through the statement handles. */; +#if 0 + /* TODO: refactor this cache as a linked list with malloc()'d entries, + rather than a fixed-size array in S3Global.envCache */ + JNIEnvCacheLine * pPrev /* Previous entry in the linked list */; + JNIEnvCacheLine * pNext /* Next entry in the linked list */; +#endif struct NphCacheLine nph[NphCache_SIZE]; }; typedef struct JNIEnvCache JNIEnvCache; @@ -335,12 +341,12 @@ struct JNIEnvCache { unsigned int used; }; -static void NphCacheLine_clear(JNIEnv *env, NphCacheLine * p){ +static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ UNREF_G(p->klazz); memset(p, 0, sizeof(NphCacheLine)); } -static void JNIEnvCacheLine_clear(JNIEnvCacheLine * p){ +static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ JNIEnv * const env = p->env; int i; if(env){ @@ -353,7 +359,7 @@ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * p){ } } -static void JNIEnvCache_clear(JNIEnvCache * p){ +static void JNIEnvCache_clear(JNIEnvCache * const p){ unsigned int i = 0; for( ; i < p->used; ++i ){ JNIEnvCacheLine_clear( &p->lines[i] ); @@ -417,16 +423,17 @@ static struct { JavaVM * jvm; struct JNIEnvCache envCache; struct { - PerDbStateJni * aUsed; - PerDbStateJni * aFree; + PerDbStateJni * aUsed /* Linked list of in-use instances */; + PerDbStateJni * aFree /* Linked list of free instances */; } perDb; struct { unsigned nphCacheHits; unsigned nphCacheMisses; unsigned envCacheHits; unsigned envCacheMisses; - unsigned nDestroy; + unsigned nDestroy /* xDestroy() calls across all types */; struct { + /* Number of calls for each type of UDF callback. */ unsigned nFunc; unsigned nStep; unsigned nFinal; @@ -442,15 +449,15 @@ static struct { recovery strategy for OOM. For sqlite3 API which can reasonably return SQLITE_NOMEM, sqlite3_malloc() should be used instead. */ -static void * s3jni_malloc(JNIEnv *env, size_t n){ - void * rv = sqlite3_malloc(n); +static void * s3jni_malloc(JNIEnv * const env, size_t n){ + void * const rv = sqlite3_malloc(n); if(n && !rv){ (*env)->FatalError(env, "Out of memory.") /* does not return */; } return rv; } -static void s3jni_free(void * p){ +static void s3jni_free(void * const p){ if(p) sqlite3_free(p); } @@ -469,7 +476,7 @@ static void s3jni_free(void * p){ ** ** Returns err_code. */ -static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ +static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zMsg){ if( db!=0 ){ if( 0==zMsg ){ sqlite3Error(db, err_code); @@ -488,7 +495,7 @@ static int s3jni_db_error(sqlite3*db, int err_code, const char *zMsg){ is silently ignored and any exceptions thrown by the method trigger a warning to stdout or stderr and then the exception is suppressed. */ -static void s3jni_call_xDestroy(JNIEnv *env, jobject jObj, jclass klazz){ +static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ if(jObj){ jmethodID method; if(!klazz){ @@ -501,7 +508,7 @@ static void s3jni_call_xDestroy(JNIEnv *env, jobject jObj, jclass klazz){ ++S3Global.metrics.nDestroy; (*env)->CallVoidMethod(env, jObj, method); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); EXCEPTION_CLEAR; } }else{ @@ -561,7 +568,7 @@ static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ (2023-07-31) tests. */ FIXME_THREADING -static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassName){ +static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char *zClassName){ /** According to: @@ -600,8 +607,9 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN } if(freeSlot){ freeSlot->zClassName = zClassName; - freeSlot->klazz = REF_G((*env)->FindClass(env, zClassName)); - EXCEPTION_CANNOT_HAPPEN; + freeSlot->klazz = (*env)->FindClass(env, zClassName); + EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); + freeSlot->klazz = REF_G(freeSlot->klazz); ++S3Global.metrics.nphCacheMisses; #if DUMP_NPH_CACHES static unsigned int cacheMisses = 0; @@ -616,11 +624,9 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv *env, const char *zClassN return freeSlot; } -static jfieldID getNativePointerField(JNIEnv *env, jclass klazz){ +static jfieldID getNativePointerField(JNIEnv * const env, jclass klazz){ jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); - IFTHREW{ - (*env)->FatalError(env, "Maintenance required: missing nativePointer field."); - } + EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); return rv; } @@ -687,7 +693,7 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam Returns NULL on OOM. pDb MUST be associated with jDb via setNativePointer(). */ -static PerDbStateJni * PerDbStateJni_alloc(JNIEnv *env, sqlite3 *pDb, jobject jDb){ +static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ PerDbStateJni * rv; assert( pDb ); if(S3Global.perDb.aFree){ @@ -808,7 +814,7 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ return a (sqlite3*) but do not take one as an argument. */ FIXME_THREADING -static PerDbStateJni * PerDbStateJni_for_db(JNIEnv *env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ +static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ PerDbStateJni * s = S3Global.perDb.aUsed; if(!jDb){ if(pDb){ @@ -1017,7 +1023,7 @@ typedef struct { /* For use with sqlite3_result/value_pointer() */ #define RESULT_JAVA_VAL_STRING "ResultJavaVal" -static ResultJavaVal * ResultJavaVal_alloc(JNIEnv *env, jobject jObj){ +static ResultJavaVal * ResultJavaVal_alloc(JNIEnv * const env, jobject jObj){ ResultJavaVal * rv = sqlite3_malloc(sizeof(ResultJavaVal)); if(rv){ rv->env = env; @@ -1046,7 +1052,7 @@ static void ResultJavaVal_finalizer(void *v){ Always use a string literal for the 2nd argument so that we can use its address as a cache key. */ -static jobject new_NativePointerHolder_object(JNIEnv *env, const char *zClassName, +static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, void * pNative){ jobject rv = 0; jclass klazz = 0; @@ -1076,15 +1082,15 @@ static jobject new_NativePointerHolder_object(JNIEnv *env, const char *zClassNam return rv; } -static jobject new_sqlite3_value_wrapper(JNIEnv *env, sqlite3_value *sv){ +static jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_value", sv); } -static jobject new_sqlite3_context_wrapper(JNIEnv *env, sqlite3_context *sv){ +static jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_context", sv); } -static jobject new_sqlite3_stmt_wrapper(JNIEnv *env, sqlite3_stmt *sv){ +static jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_stmt", sv); } @@ -1118,7 +1124,7 @@ typedef struct { jmethodID jmidxInverse; } UDFState; -static UDFState * UDFState_alloc(JNIEnv *env, jobject jObj){ +static UDFState * UDFState_alloc(JNIEnv * const env, jobject jObj){ UDFState * const s = sqlite3_malloc(sizeof(UDFState)); if(s){ const char * zFSI = /* signature for xFunc, xStep, xInverse */ @@ -1428,7 +1434,7 @@ static int s3jni_busy_handler(void* pState, int n){ rc = (*env)->CallIntMethod(env, ps->busyHandler.jObj, ps->busyHandler.midCallback, (jint)n); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_WARN_CALLBACK_THREW("busy-handler callback"); EXCEPTION_CLEAR; rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "busy-handle callback threw."); } @@ -1477,7 +1483,7 @@ JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ /** Wrapper for sqlite3_close(_v2)(). */ -static jint s3jni_close_db(JNIEnv *env, jobject jDb, int version){ +static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; PerDbStateJni * ps = 0; assert(version == 1 || version == 2); @@ -1529,7 +1535,7 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, ps->collationNeeded.midCallback, ps->jDb, (jint)eTextRep, jName); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_WARN_CALLBACK_THREW("collation-needed callback"); EXCEPTION_CLEAR; s3jni_db_error(ps->pDb, SQLITE_ERROR, "collation-needed hook threw."); } @@ -1608,7 +1614,7 @@ JDECL(jlong,1column_1int64)(JENV_JSELF, jobject jpStmt, Java String of exactly half that length, returning NULL if !p or (*env)->NewString() fails. */ -static jstring s3jni_text_to_jstring(JNIEnv *env, const void * const p, int nP){ +static jstring s3jni_text_to_jstring(JNIEnv * const env, const void * const p, int nP){ return p ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) : NULL; @@ -1618,7 +1624,7 @@ static jstring s3jni_text_to_jstring(JNIEnv *env, const void * const p, int nP){ Creates a new jByteArray of length nP, copies p's contents into it, and returns that byte array. */ -static jbyteArray s3jni_new_jbyteArray(JNIEnv *env, const unsigned char * const p, int nP){ +static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); if(jba){ (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); @@ -1671,7 +1677,7 @@ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } -static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv *env,jobject jDb, +static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, jobject jHook){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; @@ -1895,7 +1901,7 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_JSELF, jobject jpDb){ bindings. Allocates the PerDbStateJni for *ppDb if *ppDb is not NULL. */ -static int s3jni_open_post(JNIEnv *env, sqlite3 **ppDb, jobject jDb, int theRc){ +static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jDb, int theRc){ if(1 && *ppDb){ PerDbStateJni * const s = PerDbStateJni_for_db(env, jDb, *ppDb, 1); if(!s && 0==theRc){ @@ -1935,7 +1941,7 @@ JDECL(jint,1open_1v2)(JENV_JSELF, jstring strName, } /* Proxy for the sqlite3_prepare[_v2/3]() family. */ -static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv *env, jclass self, +static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ @@ -1970,17 +1976,17 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv *env, jclass self, (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } -JDECL(jint,1prepare)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, +JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, jint nMax, jobject jOutStmt, jobject outTail){ return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v2)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, +JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, jint nMax, jobject jOutStmt, jobject outTail){ return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v3)(JNIEnv *env, jclass self, jobject jpDb, jbyteArray baSql, +JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax, prepFlags, jOutStmt, outTail); @@ -2047,7 +2053,7 @@ JDECL(jint,1reset)(JENV_JSELF, jobject jpStmt){ /* sqlite3_result_text/blob() and friends. */ static void result_blob_text(int asBlob, int as64, int eTextRep/*only for (asBlob=0)*/, - JNIEnv *env, sqlite3_context *pCx, + JNIEnv * const env, sqlite3_context *pCx, jbyteArray jBa, jlong nMax){ if(jBa){ jbyte * const pBuf = JBA_TOC(jBa); @@ -2278,8 +2284,8 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ } MARKER(("WARNING: created new sqlite3_stmt wrapper for TRACE_PROFILE. stmt@%p\n" "This means we have missed a route into the tracing API and it " - "needs the stmt_set_current() treatment littered around a handful " - "of other functions.\n", pP)); + "needs the stmt_set_current() treatment which is littered around " + "a handful of other functions in this file.\n", pP)); } break; case SQLITE_TRACE_ROW: @@ -2297,7 +2303,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ ps->trace.midCallback, (jint)traceflag, jP, jX); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW1("sqlite3_trace_v2() callback"); + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); rc = SQLITE_ERROR; } UNREF_L(jPUnref); @@ -2346,7 +2352,7 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, ps->updateHook.midCallback, (jint)opId, jDbName, jTable, (jlong)nRowid); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW; + EXCEPTION_WARN_CALLBACK_THREW("update hook"); EXCEPTION_CLEAR; s3jni_db_error(ps->pDb, SQLITE_ERROR, "update hook callback threw."); } @@ -2458,7 +2464,7 @@ JDECL(jbyteArray,1value_1text_1utf8)(JENV_JSELF, jobject jpSVal){ return s3jni_new_jbyteArray(env, p, n); } -static jbyteArray value_text16(int mode, JNIEnv *env, jobject jpSVal){ +static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ int const nLen = sqlite3_value_bytes16(PtrGet_sqlite3_value(jpSVal)); jbyteArray jba; const jbyte * pBytes; @@ -2535,7 +2541,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ sqlite3.h. */ JNIEXPORT void JNICALL -Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv *env, jclass self, jobject sJni){ +Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jobject sJni){ typedef struct { const char *zName; int value; @@ -2582,10 +2588,10 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv *env, jclass self, jobject sJni){ for( pLimit = &aLimits[0]; pLimit->zName; ++pLimit ){ fieldId = (*env)->GetStaticFieldID(env, klazz, pLimit->zName, "I"); - EXCEPTION_CANNOT_HAPPEN; + EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); //MARKER(("Setting %s (field=%p) = %d\n", pLimit->zName, fieldId, pLimit->value)); assert(fieldId); (*env)->SetStaticIntField(env, klazz, fieldId, (jint)pLimit->value); - EXCEPTION_CANNOT_HAPPEN; + EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed."); } } diff --git a/manifest b/manifest index 161ddef685..c587a498e1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sJNI-related\sdoc\stweaks\sand\scode\sre-ordering. -D 2023-08-01T08:48:13.434 +C Internal\sexception-handling\scleanups\sin\sthe\sJNI\sbindings. +D 2023-08-01T09:44:26.568 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,8 +231,8 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 -F ext/jni/README.md c0e6e80935e7761acead89b69c87765b23a6bcb2858c321c3d05681fd338292a -F ext/jni/src/c/sqlite3-jni.c c67b9ae6ee3eda2c89523ccddc6fd98bc610c3ac1c55dfdca2971587ff4795ef +F ext/jni/README.md d5cfc3037236dee3efa1c5ce62ddee8ad9d6d43c329a10a491888f272e90edc8 +F ext/jni/src/c/sqlite3-jni.c a2f53b91a905a2ac0cc5a71a9157c6ec684d64a343a59b0c503a61bd12163aa6 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 459db332af6ea358b42bac096b9d26f1045b9ec32fad8463bca06807b2396b2c -R 2116d1859999fef41708d9f5ffc75a3f +P b663b27e425966f34fb62482a18048f0e2934380e5c411ae3627f1fe6a765c04 +R 2f5470dcd701417296bafb0eecce52d9 U stephan -Z e9112cb724b07771e50f3afa4c070a7b +Z 5b2b767d2f48d93a46b3cc4865630bdc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1de4eef727..a5b011519b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b663b27e425966f34fb62482a18048f0e2934380e5c411ae3627f1fe6a765c04 \ No newline at end of file +057b1d4f6ffff73c120566895f61ea3fd8118968464d67ec9262096a7aa03f39 \ No newline at end of file From ed77e13f05b69878712ce0a9142d0c269afb3ea2 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 1 Aug 2023 10:19:05 +0000 Subject: [PATCH 040/148] More docs for the Java side of the JNI bindings. FossilOrigin-Name: d8e9bcee96b90d56701f7907a8bd48853211caf757e1aa8decc7ed25eece6770 --- ext/jni/src/c/sqlite3-jni.c | 2 +- ext/jni/src/org/sqlite/jni/SQLFunction.java | 67 ++++++++++++++++----- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 8 +++ manifest | 16 ++--- manifest.uuid | 2 +- 5 files changed, 70 insertions(+), 25 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index f7322cea53..52983afb64 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2520,7 +2520,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ S3Global.metrics.envCacheMisses, S3Global.metrics.envCacheHits); puts("UDF calls:"); -#define UDF(T) printf("\t%-8s = %u\n", #T, S3Global.metrics.udf.n##T) +#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3Global.metrics.udf.n##T) UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); #undef UDF printf("xDestroy calls across all callback types: %u\n", diff --git a/ext/jni/src/org/sqlite/jni/SQLFunction.java b/ext/jni/src/org/sqlite/jni/SQLFunction.java index 289f3a0396..21e5fe622a 100644 --- a/ext/jni/src/org/sqlite/jni/SQLFunction.java +++ b/ext/jni/src/org/sqlite/jni/SQLFunction.java @@ -17,22 +17,29 @@ 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 - inner classes Scalar, Aggregate, and Window. + functions in Java. + + This class is not used by itself, but is a marker base class. The + three UDF types are modelled by the inner classes Scalar, + Aggregate, and Window. Most simply, clients may create + anonymous classes from those to implement UDFs. Clients are free to + create their own classes for use with UDFs, so long as they conform + to the public interfaces defined by those three classes. The JNI + layer only actively relies on the SQLFunction base class. */ public abstract class SQLFunction { /** - ContextMap is a helper for use with aggregate and window - functions, to help them manage their accumulator state across - calls to the UDF's callbacks. + PerContextState assists aggregate and window functions in + managinga their accumulator state across calls to the UDF's + callbacks. If a given aggregate or window function is called multiple times in a single SQL statement, e.g. SELECT MYFUNC(A), MYFUNC(B)..., then the clients need some way of knowing which call is which so that they can map their state between their various UDF callbacks - and reset it (if needed) via xFinal(). This class takes care of - such mappings. + and reset it via xFinal(). This class takes care of such + mappings. This class works by mapping sqlite3_context.getAggregateContext() to a single piece of @@ -42,9 +49,10 @@ public abstract class SQLFunction { This class is a helper providing commonly-needed functionality - it is not required for use with aggregate or window functions. Client UDFs are free to perform such mappings using custom - approaches. + approaches. The provided Aggregate and Window classes + use this. */ - public static final class ContextMap { + public static final class PerContextState { private final java.util.Map> map = new java.util.HashMap<>(); @@ -76,8 +84,8 @@ public abstract class SQLFunction { associated with cx.getAggregateContext() from the map and returns it, returning null if no other UDF method has been called to set up such a mapping. The latter condition will be - the case if an aggregate is used in a statement which has no - result rows. + the case if a UDF is used in a statement which has no result + rows. */ public T takeAggregateState(sqlite3_context cx){ final ValueHolder h = map.remove(cx.getAggregateContext()); @@ -87,7 +95,10 @@ public abstract class SQLFunction { //! Subclass for creating scalar functions. public static abstract class Scalar extends SQLFunction { + + //! As for the xFunc() argument of the C API's sqlite3_create_function() public abstract void xFunc(sqlite3_context cx, sqlite3_value[] args); + /** Optionally override to be notified when the UDF is finalized by SQLite. @@ -102,20 +113,42 @@ public abstract class SQLFunction { takeAggregateState() methods. */ public static abstract class Aggregate extends SQLFunction { + + //! As for the xStep() argument of the C API's sqlite3_create_function() public abstract void xStep(sqlite3_context cx, sqlite3_value[] args); + + //! As for the xFinal() argument of the C API's sqlite3_create_function() public abstract void xFinal(sqlite3_context cx); - //! @see Scalar#xDestroy() + /** + Optionally override to be notified when the UDF is finalized by + SQLite. + */ public void xDestroy() {} - private final ContextMap map = new ContextMap<>(); + //! Per-invocation state for the UDF. + private final PerContextState map = new PerContextState<>(); - //! @see ContextMap#getAggregateState() + /** + To be called from the implementation's xStep() method, as well + as the xValue() and xInverse() methods of the Window + subclass, to fetch the current per-call UDF state. On the + first call to this method for any given sqlite3_context + argument, the context is set to the given initial value. On all other + calls, the 2nd argument is ignored. + + @see PerContextState#takeAggregateState() + */ protected final ValueHolder getAggregateState(sqlite3_context cx, T initialValue){ return map.getAggregateState(cx, initialValue); } - //! @see ContextMap#takeAggregateState() + /** + To be called from the implementation's xFinal() method to fetch + the final state of the UDF and remove its mapping. + + @see PerContextState#takeAggregateState() + */ protected final T takeAggregateState(sqlite3_context cx){ return map.takeAggregateState(cx); } @@ -129,7 +162,11 @@ public abstract class SQLFunction { invocation-specific state. */ public static abstract class Window extends Aggregate { + + //! As for the xInverse() argument of the C API's sqlite3_create_window_function() public abstract void xInverse(sqlite3_context cx, sqlite3_value[] args); + + //! As for the xValue() argument of the C API's sqlite3_create_window_function() public abstract void xValue(sqlite3_context cx); } } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 628aa86bfb..07ab83e647 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -288,6 +288,14 @@ public final class SQLite3Jni { int eTextRep, @NotNull Collation col); + /** + The Java counterpart to the C-native sqlite3_create_function(), + sqlite3_create_function_v2(), and + sqlite3_create_window_function(). Which one it behaves like + depends on which methods the final argument implements. See + SQLFunction's inner classes (Scalar, Aggregate, and Window) + for details. + */ public static native int sqlite3_create_function(@NotNull sqlite3 db, @NotNull String functionName, int nArg, int eTextRep, diff --git a/manifest b/manifest index c587a498e1..6f789b067f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sexception-handling\scleanups\sin\sthe\sJNI\sbindings. -D 2023-08-01T09:44:26.568 +C More\sdocs\sfor\sthe\sJava\sside\sof\sthe\sJNI\sbindings. +D 2023-08-01T10:19:05.727 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 F ext/jni/README.md d5cfc3037236dee3efa1c5ce62ddee8ad9d6d43c329a10a491888f272e90edc8 -F ext/jni/src/c/sqlite3-jni.c a2f53b91a905a2ac0cc5a71a9157c6ec684d64a343a59b0c503a61bd12163aa6 +F ext/jni/src/c/sqlite3-jni.c dc0a2de7249f8e76ba729cd0d6248d373fce63e565611fa0d868837f6fe58a0e F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -242,8 +242,8 @@ F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2 F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd7e02592a3734df8887a22a9f74b12751 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 -F ext/jni/src/org/sqlite/jni/SQLFunction.java 663a4e479ec65bfbf893586439e12d30b8237898064a22ab64f5658b57315f37 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java b522c6456ab66026af5c487e4ac40410be36374d0550c2a03ea28421c4d39029 +F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java d654fb8a43504b91059739eb0d435127423a195bd8f321b6c7aeedd394ed5887 F ext/jni/src/org/sqlite/jni/Tester1.java 9443cdbd2b10f6a8e1f3abd1694983a16b17960f8ed2f7e06bcc7e535fb5abcf F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b663b27e425966f34fb62482a18048f0e2934380e5c411ae3627f1fe6a765c04 -R 2f5470dcd701417296bafb0eecce52d9 +P 057b1d4f6ffff73c120566895f61ea3fd8118968464d67ec9262096a7aa03f39 +R f7f15dd74dc41170d62a8df321c4d11e U stephan -Z 5b2b767d2f48d93a46b3cc4865630bdc +Z e680719e67af1a30c3a9b7bfb130a5b0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a5b011519b..146bf34689 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -057b1d4f6ffff73c120566895f61ea3fd8118968464d67ec9262096a7aa03f39 \ No newline at end of file +d8e9bcee96b90d56701f7907a8bd48853211caf757e1aa8decc7ed25eece6770 \ No newline at end of file From decc1425a355a89b005baec36d067f0d0a8f5c8d Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 2 Aug 2023 17:20:52 +0000 Subject: [PATCH 041/148] Internal doc additions in the JNI bits. FossilOrigin-Name: b56643644e065b03fe2c3545e02ed3b2f6fd9f77c39aa84a02a00d695e9a12dc --- ext/jni/README.md | 2 ++ ext/jni/src/c/sqlite3-jni.c | 23 ++++++++++++++--------- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index b668d61386..a1a1dadc8b 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -202,6 +202,8 @@ int sqlite3_create_function(sqlite3 db, String funcName, int nArgs, int encoding, SQLFunction func); ``` +> Design question: does the encoding argument serve any purpose in JS? + `SQLFunction` is not used directly, but is instead instantiated via one of its three subclasses: diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 52983afb64..cbd0234515 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1869,8 +1869,13 @@ JDECL(jint,1initialize)(JENV_JSELF){ } /** - Sets jc->currentStmt to the 2nd arugment and returns the previous value - of jc->currentStmt. + Sets jc->currentStmt to the 2nd arugment and returns the previous + value of jc->currentStmt. This must always be called in pairs: once + to replace the current statement with the call-local one, and once + to restore it. It must be used in any stmt-handling routines which + may lead to the tracing callback being called, as the current stmt + is needed for certain tracing flags. At a minumum those ops are: + step, reset, finalize, prepare. */ static jobject stmt_set_current(JNIEnvCacheLine * const jc, jobject jStmt){ jobject const old = jc->currentStmt; @@ -2243,14 +2248,14 @@ JDECL(jint,1shutdown)(JENV_JSELF){ } JDECL(jint,1step)(JENV_JSELF,jobject jStmt){ - jobject jPrevStmt = 0; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); - int rc; + int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); - if(!pStmt) return SQLITE_MISUSE; - jPrevStmt = stmt_set_current(jc, jStmt); - rc = sqlite3_step(pStmt); - (void)stmt_set_current(jc, jPrevStmt); + if(pStmt){ + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + jobject const jPrevStmt = stmt_set_current(jc, jStmt); + rc = sqlite3_step(pStmt); + (void)stmt_set_current(jc, jPrevStmt); + } return rc; } diff --git a/manifest b/manifest index 6f789b067f..5d231852be 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sdocs\sfor\sthe\sJava\sside\sof\sthe\sJNI\sbindings. -D 2023-08-01T10:19:05.727 +C Internal\sdoc\sadditions\sin\sthe\sJNI\sbits. +D 2023-08-02T17:20:52.666 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,8 +231,8 @@ F ext/icu/README.txt 7ab7ced8ae78e3a645b57e78570ff589d4c672b71370f5aa9e1cd7024f4 F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 -F ext/jni/README.md d5cfc3037236dee3efa1c5ce62ddee8ad9d6d43c329a10a491888f272e90edc8 -F ext/jni/src/c/sqlite3-jni.c dc0a2de7249f8e76ba729cd0d6248d373fce63e565611fa0d868837f6fe58a0e +F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf +F ext/jni/src/c/sqlite3-jni.c 18be9aa295f90d166499c72a13190878aec260a95be6d5f47981975b3a371ec3 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 057b1d4f6ffff73c120566895f61ea3fd8118968464d67ec9262096a7aa03f39 -R f7f15dd74dc41170d62a8df321c4d11e +P d8e9bcee96b90d56701f7907a8bd48853211caf757e1aa8decc7ed25eece6770 +R 93d701bbf85c406f122f21e563c584fb U stephan -Z e680719e67af1a30c3a9b7bfb130a5b0 +Z 293b70ee8959f9ac1b9bd9db9ccedd82 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 146bf34689..b3978d9f93 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d8e9bcee96b90d56701f7907a8bd48853211caf757e1aa8decc7ed25eece6770 \ No newline at end of file +b56643644e065b03fe2c3545e02ed3b2f6fd9f77c39aa84a02a00d695e9a12dc \ No newline at end of file From ed86e0a0a79fd3ea9ce2b6194702903fb09f19ee Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 2 Aug 2023 17:29:18 +0000 Subject: [PATCH 042/148] More JNI-internal docs. Correct handling of a Java exception if a trace callback throws. FossilOrigin-Name: 306b269a01037bc5c98276276fdb17b37027d1ee0d603183f42a65966245bdff --- ext/jni/src/c/sqlite3-jni.c | 11 +++++++++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index cbd0234515..786521ead8 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1904,10 +1904,15 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_JSELF, jobject jpDb){ /** Code common to both the sqlite3_open() and sqlite3_open_v2() bindings. Allocates the PerDbStateJni for *ppDb if *ppDb is not - NULL. + NULL. jDb must be the org.sqlite.jni.sqlite3 object which will hold + the db's native pointer. theRc must be the result code of the open + op(). If allocation of the PerDbStateJni object fails then *ppDb is + passed to sqlite3_close(), *ppDb is assigned to 0, and SQLITE_NOMEM + is returned, else theRc is returned. In in case, *ppDb is stored in + jDb's native pointer property (even if it's NULL). */ static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jDb, int theRc){ - if(1 && *ppDb){ + if(*ppDb){ PerDbStateJni * const s = PerDbStateJni_for_db(env, jDb, *ppDb, 1); if(!s && 0==theRc){ sqlite3_close(*ppDb); @@ -2004,6 +2009,7 @@ static int s3jni_progress_handler_impl(void *pP){ int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, ps->progress.midCallback); IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_progress_handler() callback"); EXCEPTION_CLEAR; rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, "sqlite3_progress_handler() callback threw."); @@ -2309,6 +2315,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ (jint)traceflag, jP, jX); IFTHREW{ EXCEPTION_WARN_CALLBACK_THREW("sqlite3_trace_v2() callback"); + EXCEPTION_CLEAR; rc = SQLITE_ERROR; } UNREF_L(jPUnref); diff --git a/manifest b/manifest index 5d231852be..1f5c4a0567 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Internal\sdoc\sadditions\sin\sthe\sJNI\sbits. -D 2023-08-02T17:20:52.666 +C More\sJNI-internal\sdocs.\sCorrect\shandling\sof\sa\sJava\sexception\sif\sa\strace\scallback\sthrows. +D 2023-08-02T17:29:18.781 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 18be9aa295f90d166499c72a13190878aec260a95be6d5f47981975b3a371ec3 +F ext/jni/src/c/sqlite3-jni.c e4ff3c044e72f6fcaee02e44603da28739c0ebdd3d88b9f23ca0642182760e82 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d8e9bcee96b90d56701f7907a8bd48853211caf757e1aa8decc7ed25eece6770 -R 93d701bbf85c406f122f21e563c584fb +P b56643644e065b03fe2c3545e02ed3b2f6fd9f77c39aa84a02a00d695e9a12dc +R e3632815d093bec50f51fd9fc57e4268 U stephan -Z 293b70ee8959f9ac1b9bd9db9ccedd82 +Z 9bbd1b4ecbe75e7fd46dfd09cd4f460b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b3978d9f93..7ed4f6638a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b56643644e065b03fe2c3545e02ed3b2f6fd9f77c39aa84a02a00d695e9a12dc \ No newline at end of file +306b269a01037bc5c98276276fdb17b37027d1ee0d603183f42a65966245bdff \ No newline at end of file From e133a0ec05bf8182e5a542dee04a5ec72ef8bc56 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 4 Aug 2023 09:53:13 +0000 Subject: [PATCH 043/148] Improve internal error handling in the JNI create_function() impl. FossilOrigin-Name: 2c88390faa108a60c8fb1eb7aad05d90f3daf4cfef14ca73987597aaf7be83c9 --- ext/jni/GNUmakefile | 24 +++++++++++++++++++++- ext/jni/src/c/sqlite3-jni.c | 17 +++++++-------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 5 +++++ manifest | 16 +++++++-------- manifest.uuid | 2 +- 5 files changed, 46 insertions(+), 18 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 424709d413..e8cc11eeec 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -39,7 +39,29 @@ SQLite3Jni.class := $(subst .java,.class,$(SQLite3Jni.java)) # $(bin.javac) -h $(dir $@) $< #all: $(sqlite3-jni.h) -JAVA_FILES := $(wildcard $(dir.src.jni)/*.java) + +# Be explicit about which Java files to compile so that we can work on +# in-progress files without requiring them to be in a compilable statae. +JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ + BusyHandler.java \ + Collation.java \ + CollationNeeded.java \ + CommitHook.java \ + NativePointerHolder.java \ + OutputPointer.java \ + ProgressHandler.java \ + RollbackHook.java \ + SQLFunction.java \ + sqlite3_context.java \ + sqlite3.java \ + SQLite3Jni.java \ + sqlite3_stmt.java \ + sqlite3_value.java \ + Tester1.java \ + Tracer.java \ + UpdateHook.java \ + ValueHolder.java \ +) CLASS_FILES := define DOTCLASS_DEPS $(1).class: $(1).java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 786521ead8..95f53c16ff 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1797,14 +1797,12 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, s = UDFState_alloc(env, jFunctor); if( !s ) return SQLITE_NOMEM; else if( UDF_UNKNOWN_TYPE==s->type ){ - UDFState_free(s); rc = s3jni_db_error(pDb, SQLITE_MISUSE, - "Cannot unambiguously determine function type."); + "Cannot unambiguously determine function type."); goto error_cleanup; } zFuncName = JSTR_TOC(jFuncName); if(!zFuncName){ - UDFState_free(s); rc = SQLITE_NOMEM; goto error_cleanup; } @@ -1824,12 +1822,15 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, xFinal = udf_xFinal; } rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, - xFunc, xStep, xFinal, - UDFState_finalizer); + xFunc, xStep, xFinal, UDFState_finalizer); } - s->zFuncName = sqlite3_mprintf("%s", zFuncName); - if(!s->zFuncName){ - rc = SQLITE_NOMEM; + if( 0==rc ){ + s->zFuncName = sqlite3_mprintf("%s", zFuncName); + if( !s->zFuncName ){ + rc = SQLITE_NOMEM; + } + } + if( 0!=rc ){ UDFState_free(s); } error_cleanup: diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 07ab83e647..de95deb588 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -288,6 +288,11 @@ public final class SQLite3Jni { int eTextRep, @NotNull Collation col); + //Potential TODO, if we can sensibly map the lower-level bits to Java: + //public static native int sqlite3_create_fts5_function(@NotNull sqlite3 db, + // @NotNull String functionName, + // @NotNull Fts5Function func); + /** The Java counterpart to the C-native sqlite3_create_function(), sqlite3_create_function_v2(), and diff --git a/manifest b/manifest index 1f5c4a0567..cda4ee7596 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sJNI-internal\sdocs.\sCorrect\shandling\sof\sa\sJava\sexception\sif\sa\strace\scallback\sthrows. -D 2023-08-02T17:29:18.781 +C Improve\sinternal\serror\shandling\sin\sthe\sJNI\screate_function()\simpl. +D 2023-08-04T09:53:13.893 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,9 +230,9 @@ 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 3d1f106e7a08bb54279c12979b31492b3dea702a732eab445dbc765120995182 +F ext/jni/GNUmakefile 7b7bcd691abe0567e914b1964804efe3ebbecdd86a05c324365ed00ce632be8f F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c e4ff3c044e72f6fcaee02e44603da28739c0ebdd3d88b9f23ca0642182760e82 +F ext/jni/src/c/sqlite3-jni.c d74352df0bad94caa6a239f5730d7f5c7669aa178c37ab536006d3254c2f9c85 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -243,7 +243,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java d654fb8a43504b91059739eb0d435127423a195bd8f321b6c7aeedd394ed5887 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 0139a6174d26b7703fcfa10945fdab55f28f424e212b08964038e0bd83744e0f F ext/jni/src/org/sqlite/jni/Tester1.java 9443cdbd2b10f6a8e1f3abd1694983a16b17960f8ed2f7e06bcc7e535fb5abcf F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b56643644e065b03fe2c3545e02ed3b2f6fd9f77c39aa84a02a00d695e9a12dc -R e3632815d093bec50f51fd9fc57e4268 +P 306b269a01037bc5c98276276fdb17b37027d1ee0d603183f42a65966245bdff +R 6be22ebb8adf8ecf4f94561b29aff52e U stephan -Z 9bbd1b4ecbe75e7fd46dfd09cd4f460b +Z ade457d4f3c6fece0dffd75e56e9b6b4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7ed4f6638a..2654675090 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -306b269a01037bc5c98276276fdb17b37027d1ee0d603183f42a65966245bdff \ No newline at end of file +2c88390faa108a60c8fb1eb7aad05d90f3daf4cfef14ca73987597aaf7be83c9 \ No newline at end of file From c0952c11a712ff839c23aa4a907d50e45d61f56c Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 4 Aug 2023 11:08:25 +0000 Subject: [PATCH 044/148] Start including fts5 customization bits into JNI, but it's far from functional. FossilOrigin-Name: abaf5edd0430e3301a11bd0acb9ce4b81b310237e1799701411db56ef7605e01 --- ext/jni/GNUmakefile | 21 +++- ext/jni/src/c/sqlite3-jni.c | 109 ++++++++++++++------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 4 + manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 104 insertions(+), 48 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index e8cc11eeec..e8cbd2fe01 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -35,10 +35,11 @@ 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) +######################################################################## +# The future of FTS5 customization in this API is as yet unclear. +# It would be a real doozy to bind to JNI. +enable.fts5 ?= 1 # Be explicit about which Java files to compile so that we can work on # in-progress files without requiring them to be in a compilable statae. @@ -62,6 +63,13 @@ JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ UpdateHook.java \ ValueHolder.java \ ) +ifeq (1,$(enable.fts5)) + JAVA_FILES += $(patsubst %,$(dir.src.jni)/%,\ + Fts5Context.java \ + Fts5ExtensionApi.java \ + Fts5Function.java \ + ) +endif CLASS_FILES := define DOTCLASS_DEPS $(1).class: $(1).java @@ -111,7 +119,6 @@ $(sqlite3.h): $(sqlite3.c): $(sqlite3.h) SQLITE_OPT := \ - -DSQLITE_ENABLE_FTS5 \ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ @@ -133,6 +140,10 @@ SQLITE_OPT := \ # for a var which gets set in all builds but only read # via assert(). +ifeq (1,$(enable.fts5)) + SQLITE_OPT += -DSQLITE_ENABLE_FTS5 +endif + 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 @@ -160,7 +171,7 @@ sqlite3-jni.dll.cflags := \ $(sqlite3-jni.h.in): $(dir.src.jni)/SQLite3Jni.class $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) - cp -p $(sqlite3-jni.h.in) $@ + cat $(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) \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 95f53c16ff..0bd38161eb 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -57,8 +57,10 @@ #ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS # define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 #endif -#ifndef SQLITE_ENABLE_FTS4 -# define SQLITE_ENABLE_FTS4 1 +#ifdef SQLITE_ENABLE_FTS5 +# ifndef SQLITE_ENABLE_FTS4 +# define SQLITE_ENABLE_FTS4 1 +# endif #endif #ifndef SQLITE_ENABLE_MATH_FUNCTIONS # define SQLITE_ENABLE_MATH_FUNCTIONS 1 @@ -180,10 +182,13 @@ /** Helpers for extracting pointers from jobjects, noting that the corresponding Java interfaces have already done the type-checking. */ -#define PtrGet_sqlite3(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3) -#define PtrGet_sqlite3_stmt(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_stmt) -#define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_value) -#define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,ClassNames.sqlite3_context) +#define PtrGet_sqlite3(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_context) +#ifdef SQLITE_ENABLE_FTS5 +#define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) +#endif /* Helpers for Java value reference management. */ #define REF_G(VAR) (*env)->NewGlobalRef(env, VAR) #define REF_L(VAR) (*env)->NewLocalRef(env, VAR) @@ -200,12 +205,20 @@ static const struct { const char * const sqlite3_context; const char * const sqlite3_value; const char * const OutputPointer_Int32; -} ClassNames = { +#ifdef SQLITE_ENABLE_FTS5 + const char * const Fts5Context; + const char * const Fts5ExtensionApi; +#endif +} S3ClassNames = { "org/sqlite/jni/sqlite3", "org/sqlite/jni/sqlite3_stmt", "org/sqlite/jni/sqlite3_context", "org/sqlite/jni/sqlite3_value", - "org/sqlite/jni/OutputPointer$Int32" + "org/sqlite/jni/OutputPointer$Int32", +#ifdef SQLITE_ENABLE_FTS5 + "org/sqlite/jni/Fts5Context", + "org/sqlite/jni/Fts5ExtensionApi" +#endif }; /** Create a trivial JNI wrapper for (int CName(void)). */ @@ -286,11 +299,11 @@ enum { Need enough space for (only) the library's NativePointerHolder types, a fixed count known at build-time. If we add more than this a fatal error will be triggered with a reminder to increase this. - This value needs to be at least the number of entries in the - ClassNames object, as that value is our upper limit. The - ClassNames entries are the keys for this particular cache. + This value needs to be, at most, the number of entries in the + S3ClassNames object, as that value is our upper limit. The + S3ClassNames entries are the keys for this particular cache. */ - NphCache_SIZE = sizeof(ClassNames) / sizeof(char const *) + NphCache_SIZE = sizeof(S3ClassNames) / sizeof(char const *) }; /** @@ -299,7 +312,7 @@ enum { typedef struct NphCacheLine NphCacheLine; struct NphCacheLine { const char * zClassName /* "full/class/Name". Must be a static string - from the ClassNames struct. */; + from the S3ClassNames struct. */; jclass klazz /* global ref to concrete NativePointerHolder class */; jmethodID midCtor /* klazz's constructor */; jfieldID fidValue /* NativePointerHolder.nativePointer and OutputPointer.X.value */; @@ -327,6 +340,9 @@ struct JNIEnvCacheLine { accounted for, so it may be best to redefine the tracing API rather than passing through the statement handles. */; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFts5Ext /* Java singleton for the Fts5ExtensionApi instance. */; +#endif #if 0 /* TODO: refactor this cache as a linked list with malloc()'d entries, rather than a fixed-size array in S3Global.envCache */ @@ -525,7 +541,7 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ If it does, we can dynamically allocate these instead. */ FIXME_THREADING -static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ +static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * const env){ struct JNIEnvCacheLine * row = 0; int i = 0; for( ; i < JNIEnvCache_SIZE; ++i ){ @@ -541,6 +557,7 @@ static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * env){ (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full."); return NULL; } + memset(row, 0, sizeof(JNIEnvCacheLine)); row->env = env; row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object")); row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long")); @@ -635,7 +652,7 @@ static jfieldID getNativePointerField(JNIEnv * const env, jclass klazz){ zClassName must be a static string so we can use its address as a cache key. */ -static void setNativePointer(JNIEnv * env, jobject ppOut, void * p, +static void setNativePointer(JNIEnv * env, jobject ppOut, const void * p, const char *zClassName){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); @@ -655,7 +672,7 @@ static void setNativePointer(JNIEnv * env, jobject ppOut, void * p, } } (*env)->SetLongField(env, ppOut, setter, (jlong)p); - IFTHREW_REPORT; + EXCEPTION_IS_FATAL("Could not set NativePointerHolder.nativePointer."); } /** @@ -858,6 +875,7 @@ static void PerDbStateJni_free_all(void){ } } + /** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. This function calls sqlite3_aggregate_context() to allocate a tiny @@ -883,7 +901,7 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, void * pAgg; int rc = 0; struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, ClassNames.sqlite3_context); + S3Global_nph_cache(env, S3ClassNames.sqlite3_context); if(cacheLine && cacheLine->klazz && cacheLine->fidSetAgg){ member = cacheLine->fidSetAgg; assert(member); @@ -914,23 +932,23 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, return rc; } -/* Sets a native int32 value in OutputPointer.Int32 object ppOut. */ -static void setOutputInt32(JNIEnv * env, jobject ppOut, int v){ +/* Sets a native int32 value in OutputPointer.Int32 object jOut. */ +static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, ClassNames.OutputPointer_Int32); + S3Global_nph_cache(env, S3ClassNames.OutputPointer_Int32); if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ setter = cacheLine->fidValue; }else{ - const jclass klazz = (*env)->GetObjectClass(env, ppOut); + const jclass klazz = (*env)->GetObjectClass(env, jOut); setter = (*env)->GetFieldID(env, klazz, "value", "I"); if(cacheLine){ assert(!cacheLine->fidValue); cacheLine->fidValue = setter; } } - (*env)->SetIntField(env, ppOut, setter, (jint)v); - IFTHREW_REPORT; + (*env)->SetIntField(env, jOut, setter, (jint)v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } static int encodingTypeIsValid(int eTextRep){ @@ -1053,7 +1071,7 @@ static void ResultJavaVal_finalizer(void *v){ its address as a cache key. */ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, - void * pNative){ + const void * pNative){ jobject rv = 0; jclass klazz = 0; jmethodID ctor = 0; @@ -1067,7 +1085,8 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zC klazz = cacheLine ? cacheLine->klazz : (*env)->FindClass(env, zClassName); - ctor = (*env)->GetMethodID(env, klazz, "", "()V"); + ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; + EXCEPTION_IS_FATAL("Cannot find constructor for class."); if(cacheLine){ assert(zClassName == cacheLine->zClassName); assert(cacheLine->klazz); @@ -1078,22 +1097,43 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zC assert(klazz); assert(ctor); rv = (*env)->NewObject(env, klazz, ctor); + EXCEPTION_IS_FATAL("No-arg constructor threw."); if(rv) setNativePointer(env, rv, pNative, zClassName); return rv; } static jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ - return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_value", sv); + return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_value, sv); } static jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ - return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_context", sv); + return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_context, sv); } static jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ - return new_NativePointerHolder_object(env, "org/sqlite/jni/sqlite3_stmt", sv); + return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_stmt, sv); } +#if 0 +#ifdef SQLITE_ENABLE_FTS5 +static jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ + return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); +} +#endif +#endif + +#ifdef SQLITE_ENABLE_FTS5 +/*static*/ jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ + JNIEnvCacheLine * const row = S3Global_env_cache(env); + if( !row->jFts5Ext ){ + row->jFts5Ext = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, + &sFts5Api/*from sqlite3.c*/); + if(row->jFts5Ext) row->jFts5Ext = REF_G(row->jFts5Ext); + } + return row->jFts5Ext; +} +#endif + enum UDFType { UDF_SCALAR = 1, UDF_AGGREGATE, @@ -1498,7 +1538,7 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); if(ps) PerDbStateJni_set_aside(ps) /* MUST come after close() because of ps->trace. */; - setNativePointer(env, jDb, 0, ClassNames.sqlite3); + setNativePointer(env, jDb, 0, S3ClassNames.sqlite3); return (jint)rc; } @@ -1891,7 +1931,7 @@ JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ JNIEnvCacheLine * const jc = S3Global_env_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_finalize(pStmt); - setNativePointer(env, jpStmt, 0, ClassNames.sqlite3_stmt); + setNativePointer(env, jpStmt, 0, S3ClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pPrev); } return rc; @@ -1921,7 +1961,7 @@ static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jDb, int theRc = SQLITE_NOMEM; } } - setNativePointer(env, jDb, *ppDb, ClassNames.sqlite3); + setNativePointer(env, jDb, *ppDb, S3ClassNames.sqlite3); return theRc; } @@ -1983,7 +2023,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); setOutputInt32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); } - setNativePointer(env, jOutStmt, pStmt, ClassNames.sqlite3_stmt); + setNativePointer(env, jOutStmt, pStmt, S3ClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } @@ -2249,8 +2289,9 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ JDECL(jint,1shutdown)(JENV_JSELF){ PerDbStateJni_free_all(); JNIEnvCache_clear(&S3Global.envCache); - /* Do not clear S3Global.jvm: it's legal to call - sqlite3_initialize() again to restart the lib. */ + /* Do not clear S3Global.jvm or the global refs to specific classes: + it's legal to call sqlite3_initialize() again to restart the + lib. */ return sqlite3_shutdown(); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index de95deb588..a2d0b88223 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -794,6 +794,10 @@ public final class SQLite3Jni { public static native int sqlite3_value_subtype(@NotNull sqlite3_value v); + /** + Cleans up all per-JNIEnv and per-db state managed by the library + then calls the C-native sqlite3_shutdown(). + */ public static native int sqlite3_shutdown(); /** diff --git a/manifest b/manifest index cda4ee7596..7bb35487bf 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\sinternal\serror\shandling\sin\sthe\sJNI\screate_function()\simpl. -D 2023-08-04T09:53:13.893 +C Start\sincluding\sfts5\scustomization\sbits\sinto\sJNI,\sbut\sit's\sfar\sfrom\sfunctional. +D 2023-08-04T11:08:25.675 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,9 +230,9 @@ 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 7b7bcd691abe0567e914b1964804efe3ebbecdd86a05c324365ed00ce632be8f +F ext/jni/GNUmakefile 4497a2f82b5d95cb409d9207f7d82c54f100eeb0cc2b05aa05e806566b6a38a1 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c d74352df0bad94caa6a239f5730d7f5c7669aa178c37ab536006d3254c2f9c85 +F ext/jni/src/c/sqlite3-jni.c 904404bc810a809c2ec7de9fc178d559ceb8c4310fec82629ac25107de3f0e28 F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -243,7 +243,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 0139a6174d26b7703fcfa10945fdab55f28f424e212b08964038e0bd83744e0f +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78c079efc177d2975932fca9cfd34dd4d2c2062feae3f6f2016d0964e5a7120c F ext/jni/src/org/sqlite/jni/Tester1.java 9443cdbd2b10f6a8e1f3abd1694983a16b17960f8ed2f7e06bcc7e535fb5abcf F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2071,8 +2071,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 306b269a01037bc5c98276276fdb17b37027d1ee0d603183f42a65966245bdff -R 6be22ebb8adf8ecf4f94561b29aff52e +P 2c88390faa108a60c8fb1eb7aad05d90f3daf4cfef14ca73987597aaf7be83c9 +R 8bcf0b9bad61c244dfd6aa8e17b480b1 U stephan -Z ade457d4f3c6fece0dffd75e56e9b6b4 +Z a12eb6e80e9b1d3771520cdb25f8972d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2654675090..2662a57a0f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2c88390faa108a60c8fb1eb7aad05d90f3daf4cfef14ca73987597aaf7be83c9 \ No newline at end of file +abaf5edd0430e3301a11bd0acb9ce4b81b310237e1799701411db56ef7605e01 \ No newline at end of file From b15223bce6d18317408065b76bd10e9a77175f4b Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 4 Aug 2023 12:44:06 +0000 Subject: [PATCH 045/148] More work towards binding FTS5 customization to JNI. Add Fts*.java files missing from previous checkin. FossilOrigin-Name: 91263178f463ca4623dd0203696eff6bcfd68abde5d2471be3f5a3edd791c52a --- ext/jni/GNUmakefile | 4 + ext/jni/src/c/sqlite3-jni.c | 141 ++++++++++++++---- ext/jni/src/c/sqlite3-jni.h | 61 ++++++++ ext/jni/src/org/sqlite/jni/Fts5Context.java | 23 +++ .../src/org/sqlite/jni/Fts5ExtensionApi.java | 65 ++++++++ ext/jni/src/org/sqlite/jni/Fts5Function.java | 27 ++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 8 - ext/jni/src/org/sqlite/jni/Tester1.java | 7 +- ext/jni/src/org/sqlite/jni/fts5_api.java | 23 +++ manifest | 24 +-- manifest.uuid | 2 +- 11 files changed, 334 insertions(+), 51 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/Fts5Context.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5Function.java create mode 100644 ext/jni/src/org/sqlite/jni/fts5_api.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index e8cbd2fe01..3c6fe198e2 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -65,6 +65,7 @@ JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ ) ifeq (1,$(enable.fts5)) JAVA_FILES += $(patsubst %,$(dir.src.jni)/%,\ + fts5_api.java \ Fts5Context.java \ Fts5ExtensionApi.java \ Fts5Function.java \ @@ -147,6 +148,9 @@ endif 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 +ifeq (1,$(enable.fts5)) + sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_Fts5ExtensionApi.h +endif sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so # ------------------------------^^^ lib prefix is requires diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 0bd38161eb..6bb2c8d9f2 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -186,9 +186,6 @@ #define PtrGet_sqlite3_stmt(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_stmt) #define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_value) #define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_context) -#ifdef SQLITE_ENABLE_FTS5 -#define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) -#endif /* Helpers for Java value reference management. */ #define REF_G(VAR) (*env)->NewGlobalRef(env, VAR) #define REF_L(VAR) (*env)->NewLocalRef(env, VAR) @@ -205,6 +202,7 @@ static const struct { const char * const sqlite3_context; const char * const sqlite3_value; const char * const OutputPointer_Int32; + const char * const OutputPointer_Int64; #ifdef SQLITE_ENABLE_FTS5 const char * const Fts5Context; const char * const Fts5ExtensionApi; @@ -215,9 +213,11 @@ static const struct { "org/sqlite/jni/sqlite3_context", "org/sqlite/jni/sqlite3_value", "org/sqlite/jni/OutputPointer$Int32", + "org/sqlite/jni/OutputPointer$Int64", #ifdef SQLITE_ENABLE_FTS5 "org/sqlite/jni/Fts5Context", "org/sqlite/jni/Fts5ExtensionApi" + "org/sqlite/jni/fts5_api" #endif }; @@ -335,13 +335,12 @@ struct JNIEnvCacheLine { sqlite3_stmt wrapper object for every tracing call which needs a stmt object. This approach is rather invasive, - however, and there may well be places - tracing may trigger which we have no - accounted for, so it may be best to - redefine the tracing API rather than - passing through the statement handles. */; + however, requiring code in all stmt + operations which can lead through the + tracing API. */; #ifdef SQLITE_ENABLE_FTS5 - jobject jFts5Ext /* Java singleton for the Fts5ExtensionApi instance. */; + jobject jFtsExt /* Global ref to Java singleton for the + Fts5ExtensionApi instance. */; #endif #if 0 /* TODO: refactor this cache as a linked list with malloc()'d entries, @@ -368,6 +367,9 @@ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ if(env){ UNREF_G(p->globalClassObj); UNREF_G(p->globalClassLong); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(p->jFtsExt); +#endif i = 0; for( ; i < NphCache_SIZE; ++i){ NphCacheLine_clear(env, &p->nph[i]); @@ -412,8 +414,9 @@ struct PerDbStateJni { it would be a different instance (and maybe even a different class) than the one the user may expect to receive. */; - PerDbStateJni * pNext /* Next entry in the available/free list */; - PerDbStateJni * pPrev /* Previous entry in the available/free list */; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFtsApi /* global ref to fts5_api object for the db. */; +#endif JniHookState busyHandler; JniHookState collation; JniHookState collationNeeded; @@ -422,6 +425,8 @@ struct PerDbStateJni { JniHookState rollbackHook; JniHookState trace; JniHookState updateHook; + PerDbStateJni * pNext /* Next entry in the available/free list */; + PerDbStateJni * pPrev /* Previous entry in the available/free list */; }; static struct { @@ -795,6 +800,9 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ UNHOOK(busyHandler, 1); #undef UNHOOK UNREF_G(s->jDb); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(s->jFtsApi); +#endif memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; @@ -951,6 +959,25 @@ static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } +/* Sets a native int64 value in OutputPointer.Int64 object jOut. */ +static void setOutputInt64(JNIEnv * env, jobject jOut, jlong v){ + jfieldID setter = 0; + struct NphCacheLine * const cacheLine = + S3Global_nph_cache(env, S3ClassNames.OutputPointer_Int64); + if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ + setter = cacheLine->fidValue; + }else{ + const jclass klazz = (*env)->GetObjectClass(env, jOut); + setter = (*env)->GetFieldID(env, klazz, "value", "I"); + if(cacheLine){ + assert(!cacheLine->fidValue); + cacheLine->fidValue = setter; + } + } + (*env)->SetIntField(env, jOut, setter, (jint)v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); +} + static int encodingTypeIsValid(int eTextRep){ switch(eTextRep){ case SQLITE_UTF8: case SQLITE_UTF16: @@ -1114,26 +1141,6 @@ static jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_stmt, sv); } -#if 0 -#ifdef SQLITE_ENABLE_FTS5 -static jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); -} -#endif -#endif - -#ifdef SQLITE_ENABLE_FTS5 -/*static*/ jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ - JNIEnvCacheLine * const row = S3Global_env_cache(env); - if( !row->jFts5Ext ){ - row->jFts5Ext = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, - &sFts5Api/*from sqlite3.c*/); - if(row->jFts5Ext) row->jFts5Ext = REF_G(row->jFts5Ext); - } - return row->jFts5Ext; -} -#endif - enum UDFType { UDF_SCALAR = 1, UDF_AGGREGATE, @@ -2582,11 +2589,83 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ #undef SO } +//////////////////////////////////////////////////////////////////////// +// End of the sqlite3_... API bindings. Next up, FTS5... +//////////////////////////////////////////////////////////////////////// +#ifdef SQLITE_ENABLE_FTS5 +/* Creates a verbose JNI Fts5 function name. */ +#define JFuncNameFts5Ext(Suffix) \ + Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix + +#define JDECLFts(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFts5Ext(Suffix) + +#define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) +static inline Fts5ExtensionApi const * s3jni_ftsext(void){ + return &sFts5Api/*singleton from sqlite3.c*/; +} +#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() + +#if 0 +static jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ + return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); +} +#endif + +static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ + JNIEnvCacheLine * const row = S3Global_env_cache(env); + if( !row->jFtsExt ){ + row->jFtsExt = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, + s3jni_ftsext()); + if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); + } + return row->jFtsExt; +} + +JDECLFts(jobject,getInstance)(JENV_JSELF){ + return s3jni_getFts5ExensionApi(env); +} + +JDECLFts(jint,xColumnCount)(JENV_JSELF,jobject jCtx){ + Fts5ExtDecl; + return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); +} + +JDECLFts(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut64){ + Fts5ExtDecl; + sqlite3_int64 nOut = 0; + int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); + if( 0==rc && jOut64 ) setOutputInt64(env, jOut64, (jlong)nOut); + return (jint)rc; +} + +JDECLFts(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ + Fts5ExtDecl; + return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); +} + +JDECLFts(jint,xPhraseSize)(JENV_JSELF,jobject jCtx, jint iPhrase){ + Fts5ExtDecl; + return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); +} + +JDECLFts(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ + Fts5ExtDecl; + sqlite3_int64 nOut = 0; + int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); + if( 0==rc && jOut64 ) setOutputInt64(env, jOut64, (jlong)nOut); + return (jint)rc; +} + + +#endif /* SQLITE_ENABLE_FTS5 */ //////////////////////////////////////////////////////////////////////// // End of the main API bindings. What follows are internal utilities. //////////////////////////////////////////////////////////////////////// + /** Called during static init of the SQLite3Jni class to sync certain compile-time constants to Java-space. diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index ee34f79fa3..aa2786f0c6 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1615,3 +1615,64 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1do_1something_1fo } #endif #endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_Fts5ExtensionApi */ + +#ifndef _Included_org_sqlite_jni_Fts5ExtensionApi +#define _Included_org_sqlite_jni_Fts5ExtensionApi +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: getInstance + * Signature: ()Lorg/sqlite/jni/Fts5ExtensionApi; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_getInstance + (JNIEnv *, jclass); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnCount + * Signature: (Lorg/sqlite/jni/Fts5Context;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnCount + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnTotalSize + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseCount + * Signature: (Lorg/sqlite/jni/Fts5Context;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount + (JNIEnv *, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseSize + * Signature: (Lorg/sqlite/jni/Fts5Context;I)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize + (JNIEnv *, jobject, jobject, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/jni/src/org/sqlite/jni/Fts5Context.java b/ext/jni/src/org/sqlite/jni/Fts5Context.java new file mode 100644 index 0000000000..e78f67d556 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Context.java @@ -0,0 +1,23 @@ +/* +** 2023-08-04 +** +** 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 (Fts5Context*) 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 final class Fts5Context extends NativePointerHolder { +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java new file mode 100644 index 0000000000..d2a75e026f --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -0,0 +1,65 @@ +/* +** 2023-08-04 +** +** 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; + +/** + FAR FROM COMPLETE and the feasibility of binding this to Java + is still undetermined. This might be removed. + + Reminder to self: the native Fts5ExtensionApi is a singleton. +*/ +public final class Fts5ExtensionApi extends NativePointerHolder { + //! Only called from JNI + private Fts5ExtensionApi(){} + private int iVersion; + + public static native Fts5ExtensionApi getInstance(); + + public native int xColumnCount(Fts5Context fcx); + public native int xRowCount(Fts5Context fcx, OutputPointer.Int64 nRow); + public native int xColumnTotalSize(Fts5Context fcx, int iCol, OutputPointer.Int64 pnToken); + public native int xPhraseCount(Fts5Context fcx); + public native int xPhraseSize(Fts5Context fcx, int iPhrase); + /************************************************************** + void *(*xUserData)(Fts5Context*); + + int (*xTokenize)(Fts5Context*, + const char *pText, int nText, + void *pCtx, + int (*xToken)(void*, int, const char*, int, int, int) + ); + + int (*xPhraseCount)(Fts5Context*); + int (*xPhraseSize)(Fts5Context*, int iPhrase); + + int (*xInstCount)(Fts5Context*, int *pnInst); + int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); + + sqlite3_int64 (*xRowid)(Fts5Context*); + int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken); + + int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, + int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) + ); + int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); + void *(*xGetAuxdata)(Fts5Context*, int bClear); + + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); + **************************************************************/ +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5Function.java b/ext/jni/src/org/sqlite/jni/Fts5Function.java new file mode 100644 index 0000000000..463ec034f5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5Function.java @@ -0,0 +1,27 @@ +/* +** 2023-08-04 +** +** 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; + +/** + Fts5Function is used in conjunction with the + sqlite3_create_fts_function() JNI-bound API to give that native code + access to the callback functions needed in order to implement + FTS5 auxiliary functions in Java. +*/ +public abstract class Fts5Function { + + public abstract void xFunction(Fts5ExtensionApi pApi, Fts5Context pFts, + sqlite3_context pCtx, sqlite3_value argv[]); + public void xDestroy() {} +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index a2d0b88223..03401dab75 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -776,14 +776,6 @@ public final class SQLite3Jni { public static native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); - //TODO: to_java() should return a closest-match type for the given - //value. The quirk is that it would need to return object-type values, - //e.g. Integer instead of int, and creating those is a bit of a nuisance - //from JNI. - //public static native Object sqlite3_value_to_java(@NotNull sqlite3_value v); - // Or we can just implement it in Java: - //public static Object sqlite3_value_to_java(@NotNull sqlite3_value v){...} - public static native int sqlite3_value_type(@NotNull sqlite3_value v); public static native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 69bf30756f..5a318e7257 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -877,7 +877,6 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testRollbackHook(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); @@ -910,6 +909,12 @@ public class Tester1 { sqlite3_close_v2(db); } + private static void testFts1(){ + Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); + affirm( null != fea ); + affirm( fea.getNativePointer() != 0 ); + } + private static void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java new file mode 100644 index 0000000000..3210493f83 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -0,0 +1,23 @@ +/* +** 2023-08-04 +** +** 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 (fts5_api*) 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 final class fts5_api extends NativePointerHolder { +} diff --git a/manifest b/manifest index 7bb35487bf..7bc8ad8f1c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Start\sincluding\sfts5\scustomization\sbits\sinto\sJNI,\sbut\sit's\sfar\sfrom\sfunctional. -D 2023-08-04T11:08:25.675 +C More\swork\stowards\sbinding\sFTS5\scustomization\sto\sJNI.\sAdd\sFts*.java\sfiles\smissing\sfrom\sprevious\scheckin. +D 2023-08-04T12:44:06.920 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,24 +230,28 @@ 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 4497a2f82b5d95cb409d9207f7d82c54f100eeb0cc2b05aa05e806566b6a38a1 +F ext/jni/GNUmakefile ff5c539c686b79598c9703822343a3b3117b2153ccebde0935f805f760de0847 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 904404bc810a809c2ec7de9fc178d559ceb8c4310fec82629ac25107de3f0e28 -F ext/jni/src/c/sqlite3-jni.h 74aaf87e77f99857aa3afc013517c934cbc2c16618c83d8f5d6294351bc8e7b1 +F ext/jni/src/c/sqlite3-jni.c 8ec2c1cc63af05bc84965c73e0dacb39474f5efd9fa712c780a24a44237c5e4b +F ext/jni/src/c/sqlite3-jni.h ed03612024e9fe06a9e0655ccfe9f9f7bd899c7e68badad636c7d9833682655b F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a +F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java a1352e38eda4f0a78c51d4e1d71bacc7aa9bd9a47f4724626399079992ffdb1f +F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd7e02592a3734df8887a22a9f74b12751 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78c079efc177d2975932fca9cfd34dd4d2c2062feae3f6f2016d0964e5a7120c -F ext/jni/src/org/sqlite/jni/Tester1.java 9443cdbd2b10f6a8e1f3abd1694983a16b17960f8ed2f7e06bcc7e535fb5abcf +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78496a02c7cc65a2238f54e935af070acf4e2dbef95d7cc1ff46938c440848a4 +F ext/jni/src/org/sqlite/jni/Tester1.java fed44197213929cb16edbb59d68f057d0b851cb35c4b52366b021e158ded304b F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee +F ext/jni/src/org/sqlite/jni/fts5_api.java 916fc40f96f1a9cb288d2d9656d88db69a684ae922c38fdf3dd947b1878bb357 F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 72a0698aeb50a183ad146cd29ee04952abb8c36021f6122656aa5ec20469f6f7 @@ -2071,8 +2075,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2c88390faa108a60c8fb1eb7aad05d90f3daf4cfef14ca73987597aaf7be83c9 -R 8bcf0b9bad61c244dfd6aa8e17b480b1 +P abaf5edd0430e3301a11bd0acb9ce4b81b310237e1799701411db56ef7605e01 +R 0ce682e16f3279fc573892c09d1e8907 U stephan -Z a12eb6e80e9b1d3771520cdb25f8972d +Z cb3ae8248e37b24a50f177e201f5ab44 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2662a57a0f..9493d6175f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -abaf5edd0430e3301a11bd0acb9ce4b81b310237e1799701411db56ef7605e01 \ No newline at end of file +91263178f463ca4623dd0203696eff6bcfd68abde5d2471be3f5a3edd791c52a \ No newline at end of file From 5e208f712f7971a2aa497d191eb2a12657014c5b Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 4 Aug 2023 13:03:31 +0000 Subject: [PATCH 046/148] FTS-related JNI refactoring. Move FTS-specific tests into their own class and dynamically load it, if possible, from the main test app. FossilOrigin-Name: b7a8428fcd969e7a29a23c2dae61883f69501094f2de0f79bbee3c02c672cbf5 --- ext/jni/GNUmakefile | 3 +- ext/jni/src/c/sqlite3-jni.c | 7 +- ext/jni/src/org/sqlite/jni/Tester1.java | 18 +++- ext/jni/src/org/sqlite/jni/TesterFts5.java | 101 +++++++++++++++++++++ manifest | 17 ++-- manifest.uuid | 2 +- 6 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/TesterFts5.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 3c6fe198e2..df626c22d3 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -69,11 +69,12 @@ ifeq (1,$(enable.fts5)) Fts5Context.java \ Fts5ExtensionApi.java \ Fts5Function.java \ + TesterFts5.java \ ) endif CLASS_FILES := define DOTCLASS_DEPS -$(1).class: $(1).java +$(1).class: $(1).java $(MAKEFILE) all: $(1).class CLASS_FILES += $(1).class endef diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 6bb2c8d9f2..07b7dd2b5c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -206,6 +206,7 @@ static const struct { #ifdef SQLITE_ENABLE_FTS5 const char * const Fts5Context; const char * const Fts5ExtensionApi; + const char * const fts5_api; #endif } S3ClassNames = { "org/sqlite/jni/sqlite3", @@ -216,7 +217,7 @@ static const struct { "org/sqlite/jni/OutputPointer$Int64", #ifdef SQLITE_ENABLE_FTS5 "org/sqlite/jni/Fts5Context", - "org/sqlite/jni/Fts5ExtensionApi" + "org/sqlite/jni/Fts5ExtensionApi", "org/sqlite/jni/fts5_api" #endif }; @@ -959,6 +960,7 @@ static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } +#ifdef SQLITE_ENABLE_FTS5 /* Sets a native int64 value in OutputPointer.Int64 object jOut. */ static void setOutputInt64(JNIEnv * env, jobject jOut, jlong v){ jfieldID setter = 0; @@ -977,6 +979,7 @@ static void setOutputInt64(JNIEnv * env, jobject jOut, jlong v){ (*env)->SetIntField(env, jOut, setter, (jint)v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } +#endif /* SQLITE_ENABLE_FTS5 */ static int encodingTypeIsValid(int eTextRep){ switch(eTextRep){ @@ -2618,7 +2621,7 @@ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ JNIEnvCacheLine * const row = S3Global_env_cache(env); if( !row->jFtsExt ){ row->jFtsExt = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, - s3jni_ftsext()); + s3jni_ftsext()); if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); } return row->jFtsExt; diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 5a318e7257..216efdeb4f 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -909,10 +909,19 @@ public class Tester1 { sqlite3_close_v2(db); } - private static void testFts1(){ - Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); - affirm( null != fea ); - affirm( fea.getNativePointer() != 0 ); + @SuppressWarnings("unchecked") + private static void testFts5(){ + try { + Class t = Class.forName("org.sqlite.jni.TesterFts5"); + java.lang.reflect.Constructor ctor = t.getConstructor(); + ctor.newInstance(); + }catch(ClassNotFoundException e){ + outln("FTS5 classes not loaded. Skipping FTS tests."); + }catch(NoSuchMethodException e){ + outln("FTS5 tester ctor not found. Skipping FTS tests."); + }catch(Exception e){ + outln("FTS5 tester cannot be instantiated. Skipping FTS tests."); + } } private static void testSleep(){ @@ -947,6 +956,7 @@ public class Tester1 { testCommitHook(); testRollbackHook(); testUpdateHook(); + testFts5(); //testSleep(); if(liArgs.indexOf("-v")>0){ sqlite3_do_something_for_developer(); diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java new file mode 100644 index 0000000000..55f282905e --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -0,0 +1,101 @@ +/* +** 2023-08-04 +** +** 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. +*/ +package org.sqlite.jni; +import static org.sqlite.jni.SQLite3Jni.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public class TesterFts5 { + + private static void out(T val){ + System.out.print(val); + } + + private static void outln(T val){ + System.out.println(val); + } + + private static int affirmCount = 0; + private static void affirm(Boolean v){ + ++affirmCount; + if( !v ) throw new RuntimeException("Assertion failed."); + } + + private static void execSql(sqlite3 db, String[] sql){ + execSql(db, String.join("", sql)); + } + private static int execSql(sqlite3 db, boolean throwOnError, 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(); + int rc = 0; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); + affirm(0 == rc); + pos = oTail.getValue(); + affirm(0 != stmt.getNativePointer()); + rc = sqlite3_step(stmt); + sqlite3_finalize(stmt); + affirm(0 == stmt.getNativePointer()); + if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ + if(throwOnError){ + throw new RuntimeException("db op failed with rc="+rc); + }else{ + break; + } + } + } + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + return rc; + } + private static void execSql(sqlite3 db, String sql){ + execSql(db, true, sql); + } + + + private static sqlite3 createNewDb(){ + sqlite3 db = new sqlite3(); + affirm(0 == db.getNativePointer()); + int rc = sqlite3_open(":memory:", db); + affirm(0 == rc); + affirm(0 != db.getNativePointer()); + rc = sqlite3_busy_timeout(db, 2000); + affirm( 0 == rc ); + return db; + } + + private static void test1(){ + Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); + affirm( null != fea ); + affirm( fea.getNativePointer() != 0 ); + affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/; + } + + public TesterFts5(){ + final long timeStart = System.nanoTime(); + test1(); + final long timeEnd = System.nanoTime(); + outln("FTS5 Tests done. Metrics:"); + outln("\tAssertions checked: "+affirmCount); + outln("\tTotal time = " + +((timeEnd - timeStart)/1000000.0)+"ms"); + } +} diff --git a/manifest b/manifest index 7bc8ad8f1c..ec8e59ad1d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\swork\stowards\sbinding\sFTS5\scustomization\sto\sJNI.\sAdd\sFts*.java\sfiles\smissing\sfrom\sprevious\scheckin. -D 2023-08-04T12:44:06.920 +C FTS-related\sJNI\srefactoring.\sMove\sFTS-specific\stests\sinto\stheir\sown\sclass\sand\sdynamically\sload\sit,\sif\spossible,\sfrom\sthe\smain\stest\sapp. +D 2023-08-04T13:03:31.867 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,9 +230,9 @@ 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 ff5c539c686b79598c9703822343a3b3117b2153ccebde0935f805f760de0847 +F ext/jni/GNUmakefile 83f8f1c5a76714b3034815631587336c9b5bb345a325bde118a6909e2f18b16f F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 8ec2c1cc63af05bc84965c73e0dacb39474f5efd9fa712c780a24a44237c5e4b +F ext/jni/src/c/sqlite3-jni.c d3f98fa1b76d2ee810984ace183459d893d322f8c78bdf44d2bacf2b5e465cf6 F ext/jni/src/c/sqlite3-jni.h ed03612024e9fe06a9e0655ccfe9f9f7bd899c7e68badad636c7d9833682655b F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -247,7 +247,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78496a02c7cc65a2238f54e935af070acf4e2dbef95d7cc1ff46938c440848a4 -F ext/jni/src/org/sqlite/jni/Tester1.java fed44197213929cb16edbb59d68f057d0b851cb35c4b52366b021e158ded304b +F ext/jni/src/org/sqlite/jni/Tester1.java 983d6eda3c5f5c48983b4dd972c59a18b51041c304918b8e697a9e133ea02821 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 1d258f7c252bb509b424095a02a54e18e63aaf6830702d929a609d63cae4a13e F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2075,8 +2076,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P abaf5edd0430e3301a11bd0acb9ce4b81b310237e1799701411db56ef7605e01 -R 0ce682e16f3279fc573892c09d1e8907 +P 91263178f463ca4623dd0203696eff6bcfd68abde5d2471be3f5a3edd791c52a +R b273b1d0d422a8c3553d79602f5aa670 U stephan -Z cb3ae8248e37b24a50f177e201f5ab44 +Z 5f6d6f36a9720080751b0cf982852db5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 9493d6175f..1e82e558b6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -91263178f463ca4623dd0203696eff6bcfd68abde5d2471be3f5a3edd791c52a \ No newline at end of file +b7a8428fcd969e7a29a23c2dae61883f69501094f2de0f79bbee3c02c672cbf5 \ No newline at end of file From c7795cfd475d6d0e0e31b2c15e287d1fb92c27ed Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 4 Aug 2023 13:27:45 +0000 Subject: [PATCH 047/148] Eliminate code duplication in the two JNI tester classes. FossilOrigin-Name: 63e7bbe3d5fcfb531f9d7fa88398c1191570e69b5d11adcb9c5e64b8345b4e6c --- ext/jni/src/org/sqlite/jni/Tester1.java | 59 +++++++++--------- ext/jni/src/org/sqlite/jni/TesterFts5.java | 72 ++-------------------- manifest | 14 ++--- manifest.uuid | 2 +- 4 files changed, 44 insertions(+), 103 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 216efdeb4f..c4fbbf5435 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -23,16 +23,16 @@ public class Tester1 { static final Metrics metrics = new Metrics(); - private static void out(T val){ + public static void out(T val){ System.out.print(val); } - private static void outln(T val){ + public static void outln(T val){ System.out.println(val); } - private static int affirmCount = 0; - private static void affirm(Boolean v){ + static int affirmCount = 0; + public static void affirm(Boolean v){ ++affirmCount; if( !v ) throw new RuntimeException("Assertion failed."); } @@ -50,21 +50,23 @@ public class Tester1 { affirm(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)+")"); - } - + public static sqlite3 createNewDb(){ + sqlite3 db = new sqlite3(); + affirm(0 == db.getNativePointer()); + int rc = sqlite3_open(":memory:", db); + ++metrics.dbOpen; + affirm(0 == rc); + affirm(0 != db.getNativePointer()); + rc = sqlite3_busy_timeout(db, 2000); + affirm( 0 == rc ); + return db; } - private static void execSql(sqlite3 db, String[] sql){ + public static void execSql(sqlite3 db, String[] sql){ execSql(db, String.join("", sql)); } - private static int execSql(sqlite3 db, boolean throwOnError, String sql){ + + public static int execSql(sqlite3 db, boolean throwOnError, String sql){ OutputPointer.Int32 oTail = new OutputPointer.Int32(); final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); int pos = 0, n = 1; @@ -95,9 +97,11 @@ public class Tester1 { if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; return rc; } - private static void execSql(sqlite3 db, String sql){ + + public static void execSql(sqlite3 db, String sql){ execSql(db, true, sql); } + private static void testOpenDb1(){ sqlite3 db = new sqlite3(); affirm(0 == db.getNativePointer()); @@ -109,6 +113,17 @@ public class Tester1 { affirm(0 == db.getNativePointer()); } + 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 testOpenDb2(){ sqlite3 db = new sqlite3(); affirm(0 == db.getNativePointer()); @@ -122,18 +137,6 @@ public class Tester1 { affirm(0 == db.getNativePointer()); } - private static sqlite3 createNewDb(){ - sqlite3 db = new sqlite3(); - affirm(0 == db.getNativePointer()); - int rc = sqlite3_open(":memory:", db); - ++metrics.dbOpen; - affirm(0 == rc); - affirm(0 != db.getNativePointer()); - rc = sqlite3_busy_timeout(db, 2000); - affirm( 0 == rc ); - return db; - } - private static void testPrepare123(){ sqlite3 db = createNewDb(); int rc; diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 55f282905e..b54ae8270a 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -13,75 +13,10 @@ */ package org.sqlite.jni; import static org.sqlite.jni.SQLite3Jni.*; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; +import static org.sqlite.jni.Tester1.*; public class TesterFts5 { - private static void out(T val){ - System.out.print(val); - } - - private static void outln(T val){ - System.out.println(val); - } - - private static int affirmCount = 0; - private static void affirm(Boolean v){ - ++affirmCount; - if( !v ) throw new RuntimeException("Assertion failed."); - } - - private static void execSql(sqlite3 db, String[] sql){ - execSql(db, String.join("", sql)); - } - private static int execSql(sqlite3 db, boolean throwOnError, 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(); - int rc = 0; - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, - sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); - affirm(0 == rc); - pos = oTail.getValue(); - affirm(0 != stmt.getNativePointer()); - rc = sqlite3_step(stmt); - sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer()); - if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ - if(throwOnError){ - throw new RuntimeException("db op failed with rc="+rc); - }else{ - break; - } - } - } - if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; - return rc; - } - private static void execSql(sqlite3 db, String sql){ - execSql(db, true, sql); - } - - - private static sqlite3 createNewDb(){ - sqlite3 db = new sqlite3(); - affirm(0 == db.getNativePointer()); - int rc = sqlite3_open(":memory:", db); - affirm(0 == rc); - affirm(0 != db.getNativePointer()); - rc = sqlite3_busy_timeout(db, 2000); - affirm( 0 == rc ); - return db; - } - private static void test1(){ Fts5ExtensionApi fea = Fts5ExtensionApi.getInstance(); affirm( null != fea ); @@ -90,12 +25,15 @@ public class TesterFts5 { } public TesterFts5(){ + int oldAffirmCount = Tester1.affirmCount; + Tester1.affirmCount = 0; final long timeStart = System.nanoTime(); test1(); final long timeEnd = System.nanoTime(); outln("FTS5 Tests done. Metrics:"); - outln("\tAssertions checked: "+affirmCount); + outln("\tAssertions checked: "+Tester1.affirmCount); outln("\tTotal time = " +((timeEnd - timeStart)/1000000.0)+"ms"); + Tester1.affirmCount = oldAffirmCount; } } diff --git a/manifest b/manifest index ec8e59ad1d..a2402ef513 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C FTS-related\sJNI\srefactoring.\sMove\sFTS-specific\stests\sinto\stheir\sown\sclass\sand\sdynamically\sload\sit,\sif\spossible,\sfrom\sthe\smain\stest\sapp. -D 2023-08-04T13:03:31.867 +C Eliminate\scode\sduplication\sin\sthe\stwo\sJNI\stester\sclasses. +D 2023-08-04T13:27:45.190 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -247,8 +247,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78496a02c7cc65a2238f54e935af070acf4e2dbef95d7cc1ff46938c440848a4 -F ext/jni/src/org/sqlite/jni/Tester1.java 983d6eda3c5f5c48983b4dd972c59a18b51041c304918b8e697a9e133ea02821 -F ext/jni/src/org/sqlite/jni/TesterFts5.java 1d258f7c252bb509b424095a02a54e18e63aaf6830702d929a609d63cae4a13e +F ext/jni/src/org/sqlite/jni/Tester1.java 70df1ad81d9740cd5c10dd2c82955b861be33434ddb722fd9b67c35f7ae6fdab +F ext/jni/src/org/sqlite/jni/TesterFts5.java b6952f742c968d57f6bc98c0e9c635a31cbcf0b0357f49b9d01e9b56895cf9ab F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2076,8 +2076,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 91263178f463ca4623dd0203696eff6bcfd68abde5d2471be3f5a3edd791c52a -R b273b1d0d422a8c3553d79602f5aa670 +P b7a8428fcd969e7a29a23c2dae61883f69501094f2de0f79bbee3c02c672cbf5 +R 59dc3fb86d2e5c0f507a4fc5e8173e50 U stephan -Z 5f6d6f36a9720080751b0cf982852db5 +Z e13f8a71cd8f22ed593837a52fb2f248 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1e82e558b6..0d6ae2989a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b7a8428fcd969e7a29a23c2dae61883f69501094f2de0f79bbee3c02c672cbf5 \ No newline at end of file +63e7bbe3d5fcfb531f9d7fa88398c1191570e69b5d11adcb9c5e64b8345b4e6c \ No newline at end of file From cc8202b64621f19d2b0e1b59f391dcde0c7b0ba0 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 4 Aug 2023 15:38:59 +0000 Subject: [PATCH 048/148] More work on the JNI binding of fts5 customization (still a long ways to go). FossilOrigin-Name: 1a246fd21657f5bb13eeacc4059894ab787ea9a3c45bd9bdd3030a66643d2fef --- ext/jni/src/c/sqlite3-jni.c | 129 +++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 48 ++++++- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 44 ++++-- ext/jni/src/org/sqlite/jni/OutputPointer.java | 28 +++- manifest | 18 +-- manifest.uuid | 2 +- 6 files changed, 219 insertions(+), 50 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 07b7dd2b5c..0a879383ca 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -203,6 +203,8 @@ static const struct { const char * const sqlite3_value; const char * const OutputPointer_Int32; const char * const OutputPointer_Int64; + const char * const OutputPointer_String; + const char * const OutputPointer_ByteArray; #ifdef SQLITE_ENABLE_FTS5 const char * const Fts5Context; const char * const Fts5ExtensionApi; @@ -215,6 +217,8 @@ static const struct { "org/sqlite/jni/sqlite3_value", "org/sqlite/jni/OutputPointer$Int32", "org/sqlite/jni/OutputPointer$Int64", + "org/sqlite/jni/OutputPointer$String", + "org/sqlite/jni/OutputPointer$ByteArray", #ifdef SQLITE_ENABLE_FTS5 "org/sqlite/jni/Fts5Context", "org/sqlite/jni/Fts5ExtensionApi", @@ -941,21 +945,33 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, return rc; } -/* Sets a native int32 value in OutputPointer.Int32 object jOut. */ -static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ +/** + Common init for setOutputInt32() and friends. +*/ +static void setupOutputPointer(JNIEnv * env, const char *zClassName, + const char *zTypeSig, + jobject jOut, jfieldID * pSetter){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, S3ClassNames.OutputPointer_Int32); + S3Global_nph_cache(env, zClassName); if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ setter = cacheLine->fidValue; }else{ const jclass klazz = (*env)->GetObjectClass(env, jOut); - setter = (*env)->GetFieldID(env, klazz, "value", "I"); + setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); + EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); if(cacheLine){ assert(!cacheLine->fidValue); cacheLine->fidValue = setter; } } + *pSetter = setter; +} + +/* Sets a native int32 value in OutputPointer.Int32 object jOut. */ +static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ + jfieldID setter = 0; + setupOutputPointer(env, S3ClassNames.OutputPointer_Int32, "I", jOut, &setter); (*env)->SetIntField(env, jOut, setter, (jint)v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } @@ -964,21 +980,34 @@ static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ /* Sets a native int64 value in OutputPointer.Int64 object jOut. */ static void setOutputInt64(JNIEnv * env, jobject jOut, jlong v){ jfieldID setter = 0; - struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, S3ClassNames.OutputPointer_Int64); - if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ - setter = cacheLine->fidValue; - }else{ - const jclass klazz = (*env)->GetObjectClass(env, jOut); - setter = (*env)->GetFieldID(env, klazz, "value", "I"); - if(cacheLine){ - assert(!cacheLine->fidValue); - cacheLine->fidValue = setter; - } - } - (*env)->SetIntField(env, jOut, setter, (jint)v); + setupOutputPointer(env, S3ClassNames.OutputPointer_Int64, "J", jOut, &setter); + (*env)->SetLongField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } +static void setOutputByteArray(JNIEnv * env, jobject jOut, jbyteArray v){ + jfieldID setter = 0; + setupOutputPointer(env, S3ClassNames.OutputPointer_ByteArray, "[B", + jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); +} +#if 0 +/* Sets a String value in OutputPointer.String object jOut. */ +static void setOutputString(JNIEnv * env, jobject jOut, jstring v){ + jfieldID setter = 0; + setupOutputPointer(env, S3ClassNames.OutputPointer_String, "Ljava/lang/String", + jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); +} +static void setOutputString2(JNIEnv * env, jobject jOut, const char * zStr){ + jstring const jStr = (*env)->NewStringUTF(env, zStr); + if(jStr){ + setOutputString(env, jOut, jStr); + UNREF_L(jStr); + } +} +#endif #endif /* SQLITE_ENABLE_FTS5 */ static int encodingTypeIsValid(int eTextRep){ @@ -2636,6 +2665,46 @@ JDECLFts(jint,xColumnCount)(JENV_JSELF,jobject jCtx){ return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); } +JDECLFts(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32){ + Fts5ExtDecl; + int n1 = 0; + int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); + if( 0==rc ) setOutputInt32(env, jOut32, n1); + return rc; +} + +JDECLFts(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, + jobject jOutBA){ + Fts5ExtDecl; + const char *pz = 0; + int pn = 0; + int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, + &pz, &pn); + if( 0==rc ){ + /* Two problems here: + + 1) JNI doesn't give us a way to create strings from standard + UTF-8. We're converting the results to MUTF-8, which may + differ for exotic text. + + 2) JNI's NewStringUTF() (which treats its input as MUTF-8) does + not take a _length_ - it requires the string to be + NUL-terminated, which may not the case here. + + So we use a byte array and convert it to UTF-8 Java-side. + */ + jbyteArray const jba = (*env)->NewByteArray(env, (jint)pn); + if( jba ){ + (*env)->SetByteArrayRegion(env, jba, 0, (jint)pn, (const jbyte*)pz); + setOutputByteArray(env, jOutBA, jba); + UNREF_L(jba)/*jOutBA has a reference*/; + }else{ + rc = SQLITE_NOMEM; + } + } + return (jint)rc; +} + JDECLFts(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; @@ -2644,6 +2713,27 @@ JDECLFts(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut return (jint)rc; } +JDECLFts(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, + jobject jOutCol, jobject jOutOff){ + Fts5ExtDecl; + int n1 = 0, n2 = 2, n3 = 0; + int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3); + if( 0==rc ){ + setOutputInt32(env, jOutPhrase, n1); + setOutputInt32(env, jOutCol, n2); + setOutputInt32(env, jOutOff, n3); + } + return rc; +} + +JDECLFts(jint,xInstCount)(JENV_JSELF,jobject jCtx, jobject jOut32){ + Fts5ExtDecl; + int nOut = 0; + int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); + if( 0==rc && jOut32 ) setOutputInt32(env, jOut32, nOut); + return (jint)rc; +} + JDECLFts(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); @@ -2662,6 +2752,11 @@ JDECLFts(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ return (jint)rc; } +JDECLFts(jlong,xRowid)(JENV_JSELF,jobject jCtx){ + Fts5ExtDecl; + return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); +} + #endif /* SQLITE_ENABLE_FTS5 */ //////////////////////////////////////////////////////////////////////// diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index aa2786f0c6..f39b7824f9 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1642,11 +1642,19 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnCount /* * Class: org_sqlite_jni_Fts5ExtensionApi - * Method: xRowCount - * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I + * Method: xColumnSize + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount - (JNIEnv *, jobject, jobject, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnSize + (JNIEnv *, jobject, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xColumnText + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/ByteArray;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText + (JNIEnv *, jobject, jobject, jint, jobject); /* * Class: org_sqlite_jni_Fts5ExtensionApi @@ -1656,6 +1664,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize (JNIEnv *, jobject, jobject, jint, jobject); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xInst + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInst + (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xInstCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInstCount + (JNIEnv *, jobject, jobject, jobject); + /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xPhraseCount @@ -1672,6 +1696,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize (JNIEnv *, jobject, jobject, jint); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowCount + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/OutputPointer/Int64;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xRowid + * Signature: (Lorg/sqlite/jni/Fts5Context;)J + */ +JNIEXPORT jlong JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowid + (JNIEnv *, jobject, jobject); + #ifdef __cplusplus } #endif diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index d2a75e026f..a8ded85df4 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -12,6 +12,7 @@ ** This file is part of the JNI bindings for the sqlite3 C API. */ package org.sqlite.jni; +import java.nio.charset.StandardCharsets; /** FAR FROM COMPLETE and the feasibility of binding this to Java @@ -26,12 +27,34 @@ public final class Fts5ExtensionApi extends NativePointerHolder because working with those from the native JNI code is unduly quirky due to a lack of @@ -24,13 +23,34 @@ package org.sqlite.jni; */ public final class OutputPointer { public static final class Int32 { - //! Only set from the JNI layer. private int value; + Int32(){this(0);} + Int32(int v){value = v;} public final int getValue(){return value;} + public final void setValue(int v){value = v;} } + public static final class Int64 { - //! Only set from the JNI layer. private long value; + Int64(){this(0);} + Int64(long v){value = v;} public final long getValue(){return value;} + public final void setValue(long v){value = v;} + } + + public static final class String { + private java.lang.String value; + String(){this(null);} + String(java.lang.String v){value = v;} + public final java.lang.String getValue(){return value;} + public final void setValue(java.lang.String v){value = v;} + } + + public static final class ByteArray { + private byte value[]; + ByteArray(){this(null);} + ByteArray(byte v[]){value = v;} + public final byte[] getValue(){return value;} + public final void setValue(byte v[]){value = v;} } } diff --git a/manifest b/manifest index a2402ef513..c7a09635a2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Eliminate\scode\sduplication\sin\sthe\stwo\sJNI\stester\sclasses. -D 2023-08-04T13:27:45.190 +C More\swork\son\sthe\sJNI\sbinding\sof\sfts5\scustomization\s(still\sa\slong\sways\sto\sgo). +D 2023-08-04T15:38:59.701 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,17 +232,17 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 83f8f1c5a76714b3034815631587336c9b5bb345a325bde118a6909e2f18b16f F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c d3f98fa1b76d2ee810984ace183459d893d322f8c78bdf44d2bacf2b5e465cf6 -F ext/jni/src/c/sqlite3-jni.h ed03612024e9fe06a9e0655ccfe9f9f7bd899c7e68badad636c7d9833682655b +F ext/jni/src/c/sqlite3-jni.c e8bbc4773c06b25696efb7a714736b1598fd70234a536ba6e4c27dd698ecd087 +F ext/jni/src/c/sqlite3-jni.h c67b4299e349c364b2f13787d629b59c1690c9d2834d3daa6913eb8609bee9a7 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java a1352e38eda4f0a78c51d4e1d71bacc7aa9bd9a47f4724626399079992ffdb1f +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 46fe8ebff4c42cc10259026cf37f77ebe82ba38fa961b49c012c0def3eb97925 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java c7868f1f4ad63435ee44d409377df7dd7e02592a3734df8887a22a9f74b12751 +F ext/jni/src/org/sqlite/jni/OutputPointer.java d37636dd3b82097792dae9c8c255b135153845407cdbc6689f15c475850d6c93 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 @@ -2076,8 +2076,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b7a8428fcd969e7a29a23c2dae61883f69501094f2de0f79bbee3c02c672cbf5 -R 59dc3fb86d2e5c0f507a4fc5e8173e50 +P 63e7bbe3d5fcfb531f9d7fa88398c1191570e69b5d11adcb9c5e64b8345b4e6c +R c11e6bcae8d215f11cf9225821fd3c49 U stephan -Z e13f8a71cd8f22ed593837a52fb2f248 +Z 8cab7db6e5db86ab7543a142f8069f36 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0d6ae2989a..5a30673df8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -63e7bbe3d5fcfb531f9d7fa88398c1191570e69b5d11adcb9c5e64b8345b4e6c \ No newline at end of file +1a246fd21657f5bb13eeacc4059894ab787ea9a3c45bd9bdd3030a66643d2fef \ No newline at end of file From 0c179dd5b4ad694da649eabf9ea763f9d7653017 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 00:40:28 +0000 Subject: [PATCH 049/148] Bind the remaining Fts5ExtensionApi methods to JNI, noting that all are completely untested because the higher-level bits needed to do so are still missing. FossilOrigin-Name: 23383c1dfd240ce47f504dd5c3402c9a31f166fbde5bb72d91309a5655074b33 --- ext/jni/src/c/sqlite3-jni.c | 317 +++++++++++++++++- ext/jni/src/c/sqlite3-jni.h | 64 ++++ .../src/org/sqlite/jni/Fts5ExtensionApi.java | 66 ++-- .../src/org/sqlite/jni/Fts5PhraseIter.java | 24 ++ manifest | 17 +- manifest.uuid | 2 +- 6 files changed, 447 insertions(+), 43 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 0a879383ca..ce43d112c0 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -346,6 +346,11 @@ struct JNIEnvCacheLine { #ifdef SQLITE_ENABLE_FTS5 jobject jFtsExt /* Global ref to Java singleton for the Fts5ExtensionApi instance. */; + struct { + jclass klazz; + jfieldID fidA; + jfieldID fidB; + } jPhraseIter; #endif #if 0 /* TODO: refactor this cache as a linked list with malloc()'d entries, @@ -368,17 +373,18 @@ static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ JNIEnv * const env = p->env; - int i; if(env){ + int i; UNREF_G(p->globalClassObj); UNREF_G(p->globalClassLong); #ifdef SQLITE_ENABLE_FTS5 UNREF_G(p->jFtsExt); + UNREF_G(p->jPhraseIter.klazz); #endif - i = 0; - for( ; i < NphCache_SIZE; ++i){ + for( i = 0; i < NphCache_SIZE; ++i ){ NphCacheLine_clear(env, &p->nph[i]); } + memset(p, 0, sizeof(JNIEnvCacheLine)); } } @@ -387,7 +393,6 @@ static void JNIEnvCache_clear(JNIEnvCache * const p){ for( ; i < p->used; ++i ){ JNIEnvCacheLine_clear( &p->lines[i] ); } - memset(p, 0, sizeof(JNIEnvCache)); } /** State for various hook callbacks. */ @@ -419,9 +424,6 @@ struct PerDbStateJni { it would be a different instance (and maybe even a different class) than the one the user may expect to receive. */; -#ifdef SQLITE_ENABLE_FTS5 - jobject jFtsApi /* global ref to fts5_api object for the db. */; -#endif JniHookState busyHandler; JniHookState collation; JniHookState collationNeeded; @@ -771,9 +773,7 @@ static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int d } UNREF_G(s->jObj); UNREF_G(s->klazz); - s->jObj = 0; - s->klazz = 0; - s->midCallback = 0; + memset(s, 0, sizeof(*s)); } /** @@ -805,9 +805,6 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ UNHOOK(busyHandler, 1); #undef UNHOOK UNREF_G(s->jDb); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(s->jFtsApi); -#endif memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; @@ -1029,7 +1026,9 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, jbyteArray jbaRhs = jbaLhs ? (*env)->NewByteArray(env, (jint)nRhs) : NULL; //MARKER(("native xCompare nLhs=%d nRhs=%d\n", nLhs, nRhs)); if(!jbaRhs){ - (*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); + s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + return 0; + //(*env)->FatalError(env, "Out of memory. Cannot allocate arrays for collation."); } (*env)->SetByteArrayRegion(env, jbaLhs, 0, (jint)nLhs, (const jbyte*)lhs); (*env)->SetByteArrayRegion(env, jbaRhs, 0, (jint)nRhs, (const jbyte*)rhs); @@ -2713,6 +2712,43 @@ JDECLFts(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut return (jint)rc; } +typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; +struct s3jni_fts5AuxData { + JNIEnv *env; + jobject jObj; +}; + +static void s3jni_fts5AuxData_xDestroy(void *x){ + if(x){ + s3jni_fts5AuxData * const p = x; + if(p->jObj){ + JNIEnv *env = p->env; + s3jni_call_xDestroy(env, p->jObj, 0); + UNREF_G(p->jObj); + } + sqlite3_free(x); + } +} + +JDECLFts(jobject,xGetAuxdata)(JENV_JSELF,jobject jCtx, jboolean bClear){ + Fts5ExtDecl; + jobject rv = 0; + s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); + if(pAux){ + if(bClear){ + if( pAux->jObj ){ + rv = REF_L(pAux->jObj); + UNREF_G(pAux->jObj); + } + /* Note that we do not call xDestroy() in this case. */ + sqlite3_free(pAux); + }else{ + rv = pAux->jObj; + } + } + return rv; +} + JDECLFts(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; @@ -2739,11 +2775,172 @@ JDECLFts(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); } +/** + Initializes jc->jPhraseIter if it needed it. +*/ +static void s3jni_phraseIter_init(JNIEnv *const env, JNIEnvCacheLine * const jc, + jobject jIter){ + if(!jc->jPhraseIter.klazz){ + jclass klazz = (*env)->GetObjectClass(env, jIter); + EXCEPTION_IS_FATAL("Cannot get class of Fts5PhraseIter object."); + jc->jPhraseIter.klazz = REF_G(klazz); + jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + jc->jPhraseIter.fidB = (*env)->GetFieldID(env, klazz, "a", "J"); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); + } +} + +/* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ +static void s3jni_phraseIter_NToJ(JNIEnv *const env, JNIEnvCacheLine const * const jc, + Fts5PhraseIter const * const pSrc, + jobject jIter){ + assert(jc->jPhraseIter.klazz); + (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidA, (jlong)pSrc->a); + EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.a field."); + (*env)->SetLongField(env, jIter, jc->jPhraseIter.fidB, (jlong)pSrc->b); + EXCEPTION_IS_FATAL("Cannot set Fts5PhraseIter.b field."); +} + +/* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ +static void s3jni_phraseIter_JToN(JNIEnv *const env, JNIEnvCacheLine const * const jc, + jobject jIter, Fts5PhraseIter * const pDest){ + assert(jc->jPhraseIter.klazz); + pDest->a = + (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidA); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); + pDest->b = + (const unsigned char *)(*env)->GetLongField(env, jIter, jc->jPhraseIter.fidB); + EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); +} + +JDECLFts(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, + jobject jIter, jobject jOutCol, + jobject jOutOff){ + Fts5ExtDecl; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + Fts5PhraseIter iter; + int rc, iCol = 0, iOff = 0; + s3jni_phraseIter_init(env, jc, jIter); + rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, + &iter, &iCol, &iOff); + if( 0==rc ){ + setOutputInt32(env, jOutCol, iCol); + setOutputInt32(env, jOutOff, iOff); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + } + return rc; +} + +JDECLFts(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, + jobject jIter, jobject jOutCol){ + Fts5ExtDecl; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + Fts5PhraseIter iter; + int rc, iCol = 0; + s3jni_phraseIter_init(env, jc, jIter); + rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, + &iter, &iCol); + if( 0==rc ){ + setOutputInt32(env, jOutCol, iCol); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); + } + return rc; +} + +JDECLFts(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, + jobject jOutCol, jobject jOutOff){ + Fts5ExtDecl; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + Fts5PhraseIter iter; + int iCol = 0, iOff = 0; + if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; + s3jni_phraseIter_JToN(env, jc, jIter, &iter); + fext->xPhraseNext(PtrGet_Fts5Context(jCtx), + &iter, &iCol, &iOff); + setOutputInt32(env, jOutCol, iCol); + setOutputInt32(env, jOutOff, iOff); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); +} + +JDECLFts(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, + jobject jOutCol){ + Fts5ExtDecl; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + Fts5PhraseIter iter; + int iCol = 0; + if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; + s3jni_phraseIter_JToN(env, jc, jIter, &iter); + fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); + setOutputInt32(env, jOutCol, iCol); + s3jni_phraseIter_NToJ(env, jc, &iter, jIter); +} + + JDECLFts(jint,xPhraseSize)(JENV_JSELF,jobject jCtx, jint iPhrase){ Fts5ExtDecl; return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); } +/** + State for use with xQueryPhrase() and xTokenize(). +*/ +struct s3jni_xQueryPhraseState { + JNIEnv *env; + Fts5ExtensionApi const * fext; + JNIEnvCacheLine const * jc; + jmethodID midCallback; + jobject jCallback; + jobject jFcx; + /* State for xTokenize() */ + struct { + const char * zPrev; + int nPrev; + jbyteArray jba; + } tok; +}; + +static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, + Fts5Context * pFcx, void *pData){ + /* TODO: confirm that the Fts5Context passed to this function is + guaranteed to be the same one passed to xQueryPhrase(). If it's + not, we'll have to create a new wrapper object on every call. */ + struct s3jni_xQueryPhraseState const * s = pData; + JNIEnv * const env = s->env; + int rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, + s->jc->jFtsExt, s->jFcx); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("xQueryPhrase callback"); + EXCEPTION_CLEAR; + rc = SQLITE_ERROR; + } + return rc; +} + +JDECLFts(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, + jobject jCallback){ + Fts5ExtDecl; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + struct s3jni_xQueryPhraseState s; + jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ){ + EXCEPTION_CLEAR; + return SQLITE_MISUSE; + } + s.env = env; + s.jc = jc; + s.jCallback = jCallback; + s.jFcx = jFcx; + s.fext = fext; + s.midCallback = (*env)->GetMethodID(env, klazz, "xCallback", + "(Lorg.sqlite.jni.Fts5ExtensionApi;" + "Lorg.sqlite.jni.Fts5Context;)I"); + EXCEPTION_IS_FATAL("Could not extract xQueryPhraseCallback.xCallback method."); + return (jint)fext->xQueryPhrase(PtrGet_Fts5Context(jFcx), iPhrase, &s, + s3jni_xQueryPhrase); +} + + JDECLFts(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; @@ -2757,6 +2954,98 @@ JDECLFts(jlong,xRowid)(JENV_JSELF,jobject jCtx){ return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); } +JDECLFts(int,xSetAuxdata)(JENV_JSELF,jobject jCtx, jobject jAux){ + Fts5ExtDecl; + int rc; + s3jni_fts5AuxData * pAux; + pAux = sqlite3_malloc(sizeof(*pAux)); + if(!pAux){ + if(jAux){ + // Emulate how xSetAuxdata() behaves when it cannot alloc + // its auxdata wrapper. + s3jni_call_xDestroy(env, jAux, 0); + } + return SQLITE_NOMEM; + } + pAux->env = env; + pAux->jObj = REF_G(jAux); + rc = fext->xSetAuxdata(PtrGet_Fts5Context(jCtx), pAux, + s3jni_fts5AuxData_xDestroy); + return rc; +} + +/** + xToken() imp for xTokenize(). + + TODO: hold on to the byte array and avoid initializing + it if passed the same (z,nZ) as a previous call. +*/ +static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, + int nZ, int iStart, int iEnd){ + int rc; + struct s3jni_xQueryPhraseState * const s = p; + JNIEnv * const env = s->env; + jbyteArray jba; + if( s->tok.zPrev == z && s->tok.nPrev == nZ ){ + jba = s->tok.jba; + }else{ + if(s->tok.jba){ + UNREF_L(s->tok.jba); + } + s->tok.zPrev = z; + s->tok.nPrev = nZ; + s->tok.jba = (*env)->NewByteArray(env, (jint)nZ); + if( !s->tok.jba ) return SQLITE_NOMEM; + jba = s->tok.jba; + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nZ, (const jbyte*)z); + } + rc = (int)(*env)->CallIntMethod(env, s->jCallback, s->midCallback, + (jint)tFlags, jba, (jint)iStart, + (jint)iEnd); + return rc; +} + +JDECLFts(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ + Fts5ExtDecl; + JNIEnvCacheLine * const jc = S3Global_env_cache(env); + struct s3jni_xQueryPhraseState s; + int rc; + jbyte * const pText = JBA_TOC(jbaText); + jsize nText = (*env)->GetArrayLength(env, jbaText); + jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; + if( !klazz ){ + EXCEPTION_CLEAR; + JBA_RELEASE(jbaText, pText); + return SQLITE_MISUSE; + } + memset(&s, 0, sizeof(s)); + s.env = env; + s.jc = jc; + s.jCallback = jCallback; + s.jFcx = jFcx; + s.fext = fext; + s.midCallback = (*env)->GetMethodID(env, klazz, "xToken", "(I[BII)I"); + IFTHREW { + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + JBA_RELEASE(jbaText, pText); + return SQLITE_ERROR; + } + s.tok.jba = REF_L(jbaText); + s.tok.zPrev = (const char *)pText; + s.tok.nPrev = (int)nText; + rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), + (const char *)pText, (int)nText, + &s, s3jni_xTokenize_xToken); + if(s.tok.jba){ + assert( s.tok.zPrev ); + UNREF_L(s.tok.jba); + } + JBA_RELEASE(jbaText, pText); + return (jint)rc; +} + #endif /* SQLITE_ENABLE_FTS5 */ //////////////////////////////////////////////////////////////////////// diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index f39b7824f9..bbecd0d53f 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1664,6 +1664,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnTotalSize (JNIEnv *, jobject, jobject, jint, jobject); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xGetAuxdata + * Signature: (Lorg/sqlite/jni/Fts5Context;Z)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xGetAuxdata + (JNIEnv *, jobject, jobject, jboolean); + /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xInst @@ -1688,6 +1696,38 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xInstCount JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount (JNIEnv *, jobject, jobject); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseFirst + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirst + (JNIEnv *, jobject, jobject, jint, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseFirstColumn + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseFirstColumn + (JNIEnv *, jobject, jobject, jint, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseNext + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNext + (JNIEnv *, jobject, jobject, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xPhraseNextColumn + * Signature: (Lorg/sqlite/jni/Fts5Context;Lorg/sqlite/jni/Fts5PhraseIter;Lorg/sqlite/jni/OutputPointer/Int32;)V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseNextColumn + (JNIEnv *, jobject, jobject, jobject, jobject); + /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xPhraseSize @@ -1696,6 +1736,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseCount JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xPhraseSize (JNIEnv *, jobject, jobject, jint); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xQueryPhrase + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/Fts5ExtensionApi/xQueryPhraseCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xQueryPhrase + (JNIEnv *, jobject, jobject, jint, jobject); + /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xRowCount @@ -1712,6 +1760,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowCount JNIEXPORT jlong JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xRowid (JNIEnv *, jobject, jobject); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xSetAuxdata + * Signature: (Lorg/sqlite/jni/Fts5Context;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata + (JNIEnv *, jobject, jobject, jobject); + +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xTokenize + * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5ExtensionApi/xTokenizeCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize + (JNIEnv *, jobject, jobject, jbyteArray, jobject); + #ifdef __cplusplus } #endif diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index a8ded85df4..ba9b03dd65 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -15,6 +15,8 @@ package org.sqlite.jni; import java.nio.charset.StandardCharsets; /** + COMPLETELY UNTESTED. + FAR FROM COMPLETE and the feasibility of binding this to Java is still undetermined. This might be removed. @@ -25,6 +27,17 @@ public final class Fts5ExtensionApi extends NativePointerHolder { + //! Updated and used only by native code. + private long a; + private long b; +} diff --git a/manifest b/manifest index c7a09635a2..87da5cf5c5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\swork\son\sthe\sJNI\sbinding\sof\sfts5\scustomization\s(still\sa\slong\sways\sto\sgo). -D 2023-08-04T15:38:59.701 +C Bind\sthe\sremaining\sFts5ExtensionApi\smethods\sto\sJNI,\snoting\sthat\sall\sare\scompletely\suntested\sbecause\sthe\shigher-level\sbits\sneeded\sto\sdo\sso\sare\sstill\smissing. +D 2023-08-05T00:40:28.312 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,15 +232,16 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 83f8f1c5a76714b3034815631587336c9b5bb345a325bde118a6909e2f18b16f F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c e8bbc4773c06b25696efb7a714736b1598fd70234a536ba6e4c27dd698ecd087 -F ext/jni/src/c/sqlite3-jni.h c67b4299e349c364b2f13787d629b59c1690c9d2834d3daa6913eb8609bee9a7 +F ext/jni/src/c/sqlite3-jni.c c4255b2371189c347734883a80bf9adf656e42f9fdd2c6507599da6b373d244a +F ext/jni/src/c/sqlite3-jni.h 0dc116afb5b310c18052666e7047a9dc0085684aeaa9a88fd5449eaba0a04969 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 46fe8ebff4c42cc10259026cf37f77ebe82ba38fa961b49c012c0def3eb97925 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java c041113c13b3a256f9692180ff2e9389c77236d63ab8a96ed204508f1b6ec52b F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc +F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee F ext/jni/src/org/sqlite/jni/OutputPointer.java d37636dd3b82097792dae9c8c255b135153845407cdbc6689f15c475850d6c93 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc @@ -2076,8 +2077,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 63e7bbe3d5fcfb531f9d7fa88398c1191570e69b5d11adcb9c5e64b8345b4e6c -R c11e6bcae8d215f11cf9225821fd3c49 +P 1a246fd21657f5bb13eeacc4059894ab787ea9a3c45bd9bdd3030a66643d2fef +R 74dda0d2a3a66f022caa42d0cdc0fd8a U stephan -Z 8cab7db6e5db86ab7543a142f8069f36 +Z d7f1bdf30cf8c990e32f56e6e1648277 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5a30673df8..16d9426193 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1a246fd21657f5bb13eeacc4059894ab787ea9a3c45bd9bdd3030a66643d2fef \ No newline at end of file +23383c1dfd240ce47f504dd5c3402c9a31f166fbde5bb72d91309a5655074b33 \ No newline at end of file From 977b6919f24882e17c805b7fa8b4f944c11be2ec Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 01:28:30 +0000 Subject: [PATCH 050/148] Start adding fts5_api to JNI. FossilOrigin-Name: 14d18fe983c83412d72fd2005a45a2b8c48d347b7bbf8ef9630ae460cff85c32 --- ext/jni/GNUmakefile | 4 +- ext/jni/src/c/sqlite3-jni.c | 93 ++++++++++++++----- ext/jni/src/c/sqlite3-jni.h | 21 +++++ .../src/org/sqlite/jni/Fts5ExtensionApi.java | 2 +- ext/jni/src/org/sqlite/jni/Tester1.java | 9 ++ ext/jni/src/org/sqlite/jni/TesterFts5.java | 6 ++ ext/jni/src/org/sqlite/jni/fts5_api.java | 10 ++ manifest | 24 ++--- manifest.uuid | 2 +- 9 files changed, 133 insertions(+), 38 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index df626c22d3..1375c97d5f 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -150,7 +150,9 @@ 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 ifeq (1,$(enable.fts5)) - sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_Fts5ExtensionApi.h + sqlite3-jni.h.in += \ + $(dir.bld.c)/org_sqlite_jni_Fts5ExtensionApi.h \ + $(dir.bld.c)/org_sqlite_jni_fts5_api.h endif sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index ce43d112c0..e865c259b9 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -432,6 +432,9 @@ struct PerDbStateJni { JniHookState rollbackHook; JniHookState trace; JniHookState updateHook; +#ifdef SQLITE_ENABLE_FTS5 + jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; +#endif PerDbStateJni * pNext /* Next entry in the available/free list */; PerDbStateJni * pPrev /* Previous entry in the available/free list */; }; @@ -805,6 +808,9 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ UNHOOK(busyHandler, 1); #undef UNHOOK UNREF_G(s->jDb); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(s->jFtsApi); +#endif memset(s, 0, sizeof(PerDbStateJni)); s->pNext = S3Global.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; @@ -2626,12 +2632,17 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ #ifdef SQLITE_ENABLE_FTS5 /* Creates a verbose JNI Fts5 function name. */ -#define JFuncNameFts5Ext(Suffix) \ +#define JFuncNameFtsXA(Suffix) \ Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix +#define JFuncNameFtsApi(Suffix) \ + Java_org_sqlite_jni_fts5_1api_ ## Suffix -#define JDECLFts(ReturnType,Suffix) \ +#define JDECLFtsXA(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ - JFuncNameFts5Ext(Suffix) + JFuncNameFtsXA(Suffix) +#define JDECLFtsApi(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsApi(Suffix) #define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) static inline Fts5ExtensionApi const * s3jni_ftsext(void){ @@ -2644,6 +2655,9 @@ static jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); } #endif +static jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ + return new_NativePointerHolder_object(env, S3ClassNames.fts5_api, sv); +} static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ JNIEnvCacheLine * const row = S3Global_env_cache(env); @@ -2655,16 +2669,49 @@ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ return row->jFtsExt; } -JDECLFts(jobject,getInstance)(JENV_JSELF){ +/* +** Return a pointer to the fts5_api pointer for database connection db. +** If an error occurs, return NULL and leave an error in the database +** handle (accessible using sqlite3_errcode()/errmsg()). +*/ +static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ + fts5_api *pRet = 0; + sqlite3_stmt *pStmt = 0; + if( SQLITE_OK==sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ){ + sqlite3_bind_pointer(pStmt, 1, (void*)&pRet, "fts5_api_ptr", NULL); + sqlite3_step(pStmt); + } + sqlite3_finalize(pStmt); + return pRet; +} + +JDECLFtsApi(jobject,getInstanceForDb)(JENV_JSELF,jobject jDb){ + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + jobject rv = 0; + if(!ps) return 0; + else if(ps->jFtsApi){ + rv = ps->jFtsApi; + }else{ + fts5_api * const pApi = s3jni_fts5_api_from_db(ps->pDb); + if( pApi ){ + rv = new_fts5_api_wrapper(env, pApi); + ps->jFtsApi = rv ? REF_G(rv) : 0; + } + } + return rv; +} + + +JDECLFtsXA(jobject,getInstance)(JENV_JSELF){ return s3jni_getFts5ExensionApi(env); } -JDECLFts(jint,xColumnCount)(JENV_JSELF,jobject jCtx){ +JDECLFtsXA(jint,xColumnCount)(JENV_JSELF,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); } -JDECLFts(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32){ +JDECLFtsXA(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32){ Fts5ExtDecl; int n1 = 0; int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); @@ -2672,7 +2719,7 @@ JDECLFts(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32){ return rc; } -JDECLFts(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, +JDECLFtsXA(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOutBA){ Fts5ExtDecl; const char *pz = 0; @@ -2704,7 +2751,7 @@ JDECLFts(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, return (jint)rc; } -JDECLFts(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut64){ +JDECLFtsXA(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); @@ -2730,7 +2777,7 @@ static void s3jni_fts5AuxData_xDestroy(void *x){ } } -JDECLFts(jobject,xGetAuxdata)(JENV_JSELF,jobject jCtx, jboolean bClear){ +JDECLFtsXA(jobject,xGetAuxdata)(JENV_JSELF,jobject jCtx, jboolean bClear){ Fts5ExtDecl; jobject rv = 0; s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); @@ -2749,7 +2796,7 @@ JDECLFts(jobject,xGetAuxdata)(JENV_JSELF,jobject jCtx, jboolean bClear){ return rv; } -JDECLFts(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, +JDECLFtsXA(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; int n1 = 0, n2 = 2, n3 = 0; @@ -2762,7 +2809,7 @@ JDECLFts(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, return rc; } -JDECLFts(jint,xInstCount)(JENV_JSELF,jobject jCtx, jobject jOut32){ +JDECLFtsXA(jint,xInstCount)(JENV_JSELF,jobject jCtx, jobject jOut32){ Fts5ExtDecl; int nOut = 0; int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); @@ -2770,7 +2817,7 @@ JDECLFts(jint,xInstCount)(JENV_JSELF,jobject jCtx, jobject jOut32){ return (jint)rc; } -JDECLFts(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ +JDECLFtsXA(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); } @@ -2814,7 +2861,7 @@ static void s3jni_phraseIter_JToN(JNIEnv *const env, JNIEnvCacheLine const * con EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); } -JDECLFts(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, +JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; @@ -2832,7 +2879,7 @@ JDECLFts(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, return rc; } -JDECLFts(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, +JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; JNIEnvCacheLine * const jc = S3Global_env_cache(env); @@ -2848,7 +2895,7 @@ JDECLFts(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, return rc; } -JDECLFts(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, +JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; JNIEnvCacheLine * const jc = S3Global_env_cache(env); @@ -2863,7 +2910,7 @@ JDECLFts(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, s3jni_phraseIter_NToJ(env, jc, &iter, jIter); } -JDECLFts(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, +JDECLFtsXA(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; JNIEnvCacheLine * const jc = S3Global_env_cache(env); @@ -2877,7 +2924,7 @@ JDECLFts(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, } -JDECLFts(jint,xPhraseSize)(JENV_JSELF,jobject jCtx, jint iPhrase){ +JDECLFtsXA(jint,xPhraseSize)(JENV_JSELF,jobject jCtx, jint iPhrase){ Fts5ExtDecl; return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); } @@ -2917,7 +2964,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, return rc; } -JDECLFts(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, +JDECLFtsXA(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; JNIEnvCacheLine * const jc = S3Global_env_cache(env); @@ -2941,7 +2988,7 @@ JDECLFts(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, } -JDECLFts(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ +JDECLFtsXA(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); @@ -2949,12 +2996,12 @@ JDECLFts(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ return (jint)rc; } -JDECLFts(jlong,xRowid)(JENV_JSELF,jobject jCtx){ +JDECLFtsXA(jlong,xRowid)(JENV_JSELF,jobject jCtx){ Fts5ExtDecl; return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); } -JDECLFts(int,xSetAuxdata)(JENV_JSELF,jobject jCtx, jobject jAux){ +JDECLFtsXA(int,xSetAuxdata)(JENV_JSELF,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; s3jni_fts5AuxData * pAux; @@ -3005,8 +3052,8 @@ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, return rc; } -JDECLFts(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, - jobject jCallback){ +JDECLFtsXA(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ Fts5ExtDecl; JNIEnvCacheLine * const jc = S3Global_env_cache(env); struct s3jni_xQueryPhraseState s; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index bbecd0d53f..9f842c8aff 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1780,3 +1780,24 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize } #endif #endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_fts5_api */ + +#ifndef _Included_org_sqlite_jni_fts5_api +#define _Included_org_sqlite_jni_fts5_api +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_fts5_api + * Method: getInstanceForDb + * Signature: (Lorg/sqlite/jni/sqlite3;)Lorg/sqlite/jni/fts5_api; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb + (JNIEnv *, jclass, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index ba9b03dd65..b7ef3b3883 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -25,7 +25,7 @@ import java.nio.charset.StandardCharsets; public final class Fts5ExtensionApi extends NativePointerHolder { //! Only called from JNI private Fts5ExtensionApi(){} - private int iVersion; + private int iVersion = 2; //! Callback type for use with xTokenize(). public static interface xTokenizeCallback { diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index c4fbbf5435..5dd6971398 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -914,16 +914,25 @@ public class Tester1 { @SuppressWarnings("unchecked") private static void testFts5(){ + Exception err = null; try { Class t = Class.forName("org.sqlite.jni.TesterFts5"); java.lang.reflect.Constructor ctor = t.getConstructor(); + ctor.setAccessible(true); ctor.newInstance(); }catch(ClassNotFoundException e){ outln("FTS5 classes not loaded. Skipping FTS tests."); + err = e; }catch(NoSuchMethodException e){ outln("FTS5 tester ctor not found. Skipping FTS tests."); + err = e; }catch(Exception e){ outln("FTS5 tester cannot be instantiated. Skipping FTS tests."); + err = e; + } + if( null != err ){ + outln("Exception: "+err); + err.printStackTrace(); } } diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index b54ae8270a..3b17c2a49b 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -22,6 +22,12 @@ public class TesterFts5 { affirm( null != fea ); affirm( fea.getNativePointer() != 0 ); affirm( fea == Fts5ExtensionApi.getInstance() )/*singleton*/; + + sqlite3 db = createNewDb(); + fts5_api fApi = fts5_api.getInstanceForDb(db); + affirm( fApi != null ); + affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ ); + sqlite3_close_v2(db); } public TesterFts5(){ diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index 3210493f83..a4cff7d176 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -14,10 +14,20 @@ package org.sqlite.jni; /** + INCOMPLETE AND COMPLETELY UNTESTED. + A wrapper for communicating C-level (fts5_api*) 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 final class fts5_api extends NativePointerHolder { + /* Only invoked by JNI */ + private fts5_api(){} + + /** + Returns the fts5_api instance associated with the given db, or + null if something goes horribly wrong. + */ + public static native fts5_api getInstanceForDb(@NotNull sqlite3 db); } diff --git a/manifest b/manifest index 87da5cf5c5..61447c603c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\sthe\sremaining\sFts5ExtensionApi\smethods\sto\sJNI,\snoting\sthat\sall\sare\scompletely\suntested\sbecause\sthe\shigher-level\sbits\sneeded\sto\sdo\sso\sare\sstill\smissing. -D 2023-08-05T00:40:28.312 +C Start\sadding\sfts5_api\sto\sJNI. +D 2023-08-05T01:28:30.501 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,16 +230,16 @@ 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 83f8f1c5a76714b3034815631587336c9b5bb345a325bde118a6909e2f18b16f +F ext/jni/GNUmakefile 9d916736e5af011664a38d27296692e237afaed41b0843f4de4e1a6136df18d3 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c c4255b2371189c347734883a80bf9adf656e42f9fdd2c6507599da6b373d244a -F ext/jni/src/c/sqlite3-jni.h 0dc116afb5b310c18052666e7047a9dc0085684aeaa9a88fd5449eaba0a04969 +F ext/jni/src/c/sqlite3-jni.c 8c62ed298ccbe46f1b59a1ce9957a75ad8426c5dd065168316d9d2e97e54988b +F ext/jni/src/c/sqlite3-jni.h 2e6450c923fe6a9c7246930d9518c1d7e0008d9cb5143868e8bd4a159fc88901 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java c041113c13b3a256f9692180ff2e9389c77236d63ab8a96ed204508f1b6ec52b +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 8922069cf785492b18b15b72c29557de4f10f6462c3c560d1b4827f731f46c3f F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee @@ -248,12 +248,12 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78496a02c7cc65a2238f54e935af070acf4e2dbef95d7cc1ff46938c440848a4 -F ext/jni/src/org/sqlite/jni/Tester1.java 70df1ad81d9740cd5c10dd2c82955b861be33434ddb722fd9b67c35f7ae6fdab -F ext/jni/src/org/sqlite/jni/TesterFts5.java b6952f742c968d57f6bc98c0e9c635a31cbcf0b0357f49b9d01e9b56895cf9ab +F ext/jni/src/org/sqlite/jni/Tester1.java ef715de2ad23ec9b982122c9e1f0dfe689d9d0d7ac6709dab2ad710811bfa50b +F ext/jni/src/org/sqlite/jni/TesterFts5.java 0b1ab9f3675a593213b0f12fabe4fbe6fd16ed994d41a1f4150e760e087099f6 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 916fc40f96f1a9cb288d2d9656d88db69a684ae922c38fdf3dd947b1878bb357 +F ext/jni/src/org/sqlite/jni/fts5_api.java 6ceb87a8aea27727ff5c40abf79aa2f391e9408eeeea921b7ca02c837979ca84 F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 72a0698aeb50a183ad146cd29ee04952abb8c36021f6122656aa5ec20469f6f7 @@ -2077,8 +2077,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 1a246fd21657f5bb13eeacc4059894ab787ea9a3c45bd9bdd3030a66643d2fef -R 74dda0d2a3a66f022caa42d0cdc0fd8a +P 23383c1dfd240ce47f504dd5c3402c9a31f166fbde5bb72d91309a5655074b33 +R 1f7e3cb1d5a03c9cd76bc5a8ab1fdcba U stephan -Z d7f1bdf30cf8c990e32f56e6e1648277 +Z 82182ddc1d375495dd36c655052d869d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 16d9426193..32314a4148 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -23383c1dfd240ce47f504dd5c3402c9a31f166fbde5bb72d91309a5655074b33 \ No newline at end of file +14d18fe983c83412d72fd2005a45a2b8c48d347b7bbf8ef9630ae460cff85c32 \ No newline at end of file From ec71e555c43662481b1d704057f9660102cc0adb Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 04:23:27 +0000 Subject: [PATCH 051/148] Bind fts5_api::xCreateFunction() to JNI and demonstrate it with a test. FossilOrigin-Name: c653bf16cbdccae05ab14059b140191afd5c17740fb78d756d8822986e54b17c --- ext/jni/GNUmakefile | 8 +- ext/jni/src/c/sqlite3-jni.c | 247 +++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 31 ++- ext/jni/src/org/sqlite/jni/Fts5.java | 38 +++ .../src/org/sqlite/jni/Fts5ExtensionApi.java | 13 +- ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java | 30 +++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 5 - ext/jni/src/org/sqlite/jni/Tester1.java | 3 +- ext/jni/src/org/sqlite/jni/TesterFts5.java | 38 +++ ext/jni/src/org/sqlite/jni/fts5_api.java | 49 +++- .../src/org/sqlite/jni/fts5_tokenizer.java | 49 ++++ manifest | 29 +- manifest.uuid | 2 +- 13 files changed, 468 insertions(+), 74 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/Fts5.java create mode 100644 ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java create mode 100644 ext/jni/src/org/sqlite/jni/fts5_tokenizer.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 1375c97d5f..1440bda5f0 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -66,9 +66,12 @@ JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ ifeq (1,$(enable.fts5)) JAVA_FILES += $(patsubst %,$(dir.src.jni)/%,\ fts5_api.java \ + fts5_tokenizer.java \ + Fts5.java \ Fts5Context.java \ Fts5ExtensionApi.java \ Fts5Function.java \ + Fts5Tokenizer.java \ TesterFts5.java \ ) endif @@ -142,6 +145,8 @@ SQLITE_OPT := \ # for a var which gets set in all builds but only read # via assert(). +SQLITE_OPFS += -g -DDEBUG -UNDEBUG + ifeq (1,$(enable.fts5)) SQLITE_OPT += -DSQLITE_ENABLE_FTS5 endif @@ -152,7 +157,8 @@ sqlite3-jni.h.in := $(dir.bld.c)/org_sqlite_jni_SQLite3Jni.h ifeq (1,$(enable.fts5)) sqlite3-jni.h.in += \ $(dir.bld.c)/org_sqlite_jni_Fts5ExtensionApi.h \ - $(dir.bld.c)/org_sqlite_jni_fts5_api.h + $(dir.bld.c)/org_sqlite_jni_fts5_api.h \ + $(dir.bld.c)/org_sqlite_jni_fts5_tokenizer.h endif sqlite3-jni.h := $(dir.src.c)/sqlite3-jni.h sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index e865c259b9..c2397921b2 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -209,6 +209,8 @@ static const struct { const char * const Fts5Context; const char * const Fts5ExtensionApi; const char * const fts5_api; + const char * const fts5_tokenizer; + const char * const Fts5Tokenizer; #endif } S3ClassNames = { "org/sqlite/jni/sqlite3", @@ -222,7 +224,9 @@ static const struct { #ifdef SQLITE_ENABLE_FTS5 "org/sqlite/jni/Fts5Context", "org/sqlite/jni/Fts5ExtensionApi", - "org/sqlite/jni/fts5_api" + "org/sqlite/jni/fts5_api", + "org/sqlite/jni/fts5_tokenizer", + "org/sqlite/jni/Fts5Tokenizer" #endif }; @@ -304,9 +308,9 @@ enum { Need enough space for (only) the library's NativePointerHolder types, a fixed count known at build-time. If we add more than this a fatal error will be triggered with a reminder to increase this. - This value needs to be, at most, the number of entries in the - S3ClassNames object, as that value is our upper limit. The - S3ClassNames entries are the keys for this particular cache. + This value needs to be exactly the number of entries in the + S3ClassNames object. The S3ClassNames entries are the keys for + this particular cache. */ NphCache_SIZE = sizeof(S3ClassNames) / sizeof(char const *) }; @@ -358,6 +362,12 @@ struct JNIEnvCacheLine { JNIEnvCacheLine * pPrev /* Previous entry in the linked list */; JNIEnvCacheLine * pNext /* Next entry in the linked list */; #endif + /** TODO: NphCacheLine *pNphHit; + + to help fast-track cache lookups, update this to point to the + most recent hit. That will speed up, e.g. the + sqlite3_value-to-Java-array loop. + */ struct NphCacheLine nph[NphCache_SIZE]; }; typedef struct JNIEnvCache JNIEnvCache; @@ -1268,49 +1278,55 @@ typedef struct { Converts the given (cx, argc, argv) into arguments for the given UDF, placing the result in the final argument. Returns 0 on success, SQLITE_NOMEM on allocation error. + + TODO: see what we can do to optimize the + new_sqlite3_value_wrapper() call. e.g. find the ctor a single time + and call it here, rather than looking it up repeatedly. */ -static int udf_args(sqlite3_context * const cx, +static int udf_args(JNIEnv *env, + sqlite3_context * const cx, int argc, sqlite3_value**argv, - UDFState * const s, - udf_jargs * const args){ + jobject * jCx, jobjectArray *jArgv){ jobjectArray ja = 0; - JNIEnv * const env = s->env; - jobject jcx = new_sqlite3_context_wrapper(s->env, cx); + jobject jcx = new_sqlite3_context_wrapper(env, cx); jint i; - args->jcx = 0; - args->jargv = 0; + *jCx = 0; + *jArgv = 0; if(!jcx) goto error_oom; - ja = (*(s->env))->NewObjectArray(s->env, argc, - S3Global_env_cache(env)->globalClassObj, - NULL); + ja = (*env)->NewObjectArray(env, argc, + S3Global_env_cache(env)->globalClassObj, + NULL); if(!ja) goto error_oom; for(i = 0; i < argc; ++i){ - jobject jsv = new_sqlite3_value_wrapper(s->env, argv[i]); + jobject jsv = new_sqlite3_value_wrapper(env, argv[i]); if(!jsv) goto error_oom; (*env)->SetObjectArrayElement(env, ja, i, jsv); UNREF_L(jsv)/*array has a ref*/; } - args->jcx = jcx; - args->jargv = ja; + *jCx = jcx; + *jArgv = ja; return 0; error_oom: sqlite3_result_error_nomem(cx); UNREF_L(jcx); UNREF_L(ja); - return 1; + return SQLITE_NOMEM; } -static int udf_report_exception(sqlite3_context * cx, UDFState *s, +static int udf_report_exception(sqlite3_context * cx, + const char *zFuncName, const char *zFuncType){ int rc; char * z = - sqlite3_mprintf("UDF %s.%s() threw. It should not do that.", - s->zFuncName, zFuncType); + sqlite3_mprintf("Client-defined function %s.%s() threw. It should " + "not do that.", + zFuncName ? zFuncName : "", zFuncType); if(z){ sqlite3_result_error(cx, z, -1); sqlite3_free(z); rc = SQLITE_ERROR; }else{ + sqlite3_result_error_nomem(cx); rc = SQLITE_NOMEM; } return rc; @@ -1325,9 +1341,9 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, UDFState * s, jmethodID xMethodID, const char * zFuncType){ - udf_jargs args; JNIEnv * const env = s->env; - int rc = udf_args(pCx, argc, argv, s, &args); + udf_jargs args = {0,0}; + int rc = udf_args(s->env, pCx, argc, argv, &args.jcx, &args.jargv); //MARKER(("%s.%s() pCx = %p\n", s->zFuncName, zFuncType, pCx)); if(rc) return rc; //MARKER(("UDF::%s.%s()\n", s->zFuncName, zFuncType)); @@ -1337,7 +1353,7 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, args.jcx, args.jargv); IFTHREW{ - rc = udf_report_exception(pCx,s, zFuncType); + rc = udf_report_exception(pCx, s->zFuncName, zFuncType); } } UNREF_L(args.jcx); @@ -1367,7 +1383,7 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s, if( 0 == rc ){ (*env)->CallVoidMethod(env, s->jObj, xMethodID, jcx); IFTHREW{ - rc = udf_report_exception(cx,s, zFuncType); + rc = udf_report_exception(cx,s->zFuncName, zFuncType); } } UNREF_L(jcx); @@ -2636,6 +2652,8 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ Java_org_sqlite_jni_Fts5ExtensionApi_ ## Suffix #define JFuncNameFtsApi(Suffix) \ Java_org_sqlite_jni_fts5_1api_ ## Suffix +#define JFuncNameFtsTok(Suffix) \ + Java_org_sqlite_jni_fts5_tokenizer_ ## Suffix #define JDECLFtsXA(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ @@ -2643,22 +2661,82 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ #define JDECLFtsApi(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ JFuncNameFtsApi(Suffix) +#define JDECLFtsTok(ReturnType,Suffix) \ + JNIEXPORT ReturnType JNICALL \ + JFuncNameFtsTok(Suffix) +#define PtrGet_fts5_api(OBJ) getNativePointer(env,OBJ,S3ClassNames.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) getNativePointer(env,OBJ,S3ClassNames.fts5_tokenizer) #define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Tokenizer) + +/** + State for binding Java-side FTS5 auxiliary functions. +*/ +typedef struct { + JNIEnv * env; /* env registered from */; + jobject jObj /* functor instance */; + jclass klazz /* jObj's class */; + char * zFuncName /* Only for error reporting and debug logging */; + jmethodID jmid /* callback member's method ID */; +} Fts5JniAux; + +static void Fts5JniAux_free(Fts5JniAux * const s){ + JNIEnv * const env = s->env; + if(env){ + /*MARKER(("FTS5 aux function cleanup: %s\n", s->zFuncName));*/ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + UNREF_G(s->jObj); + UNREF_G(s->klazz); + } + sqlite3_free(s->zFuncName); + sqlite3_free(s); +} + +static void Fts5JniAux_xDestroy(void *p){ + if(p) Fts5JniAux_free(p); +} + +static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ + Fts5JniAux * s = sqlite3_malloc(sizeof(Fts5JniAux)); + if(s){ + const char * zSig = + "(Lorg/sqlite/jni/Fts5ExtensionApi;" + "Lorg/sqlite/jni/Fts5Context;" + "Lorg/sqlite/jni/sqlite3_context;" + "[Lorg/sqlite/jni/sqlite3_value;)V"; + memset(s, 0, sizeof(Fts5JniAux)); + s->env = env; + s->jObj = REF_G(jObj); + s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); + EXCEPTION_IS_FATAL("Cannot get class for FTS5 aux function object."); + s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + Fts5JniAux_free(s); + s = 0; + } + } + return s; +} + static inline Fts5ExtensionApi const * s3jni_ftsext(void){ return &sFts5Api/*singleton from sqlite3.c*/; } #define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() -#if 0 static jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); } -#endif static jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ return new_NativePointerHolder_object(env, S3ClassNames.fts5_api, sv); } +/** + Returns a per-JNIEnv global ref to the Fts5ExtensionApi singleton + instance, or NULL on OOM. +*/ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ JNIEnvCacheLine * const row = S3Global_env_cache(env); if( !row->jFtsExt ){ @@ -2670,9 +2748,9 @@ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ } /* -** Return a pointer to the fts5_api pointer for database connection db. -** If an error occurs, return NULL and leave an error in the database -** handle (accessible using sqlite3_errcode()/errmsg()). +** Return a pointer to the fts5_api instance for database connection +** db. If an error occurs, return NULL and leave an error in the +** database handle (accessible using sqlite3_errcode()/errmsg()). */ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ fts5_api *pRet = 0; @@ -2759,6 +2837,73 @@ JDECLFtsXA(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jO return (jint)rc; } +/** + Proxy for fts5_extension_function instances plugged in via + fts5_api::xCreateFunction(). +*/ +static void s3jni_fts5_extension_function(Fts5ExtensionApi const *pApi, + Fts5Context *pFts, + sqlite3_context *pCx, + int argc, + sqlite3_value **argv){ + Fts5JniAux * const pAux = pApi->xUserData(pFts); + JNIEnv *env; + jobject jpCx = 0; + jobjectArray jArgv = 0; + jobject jpFts = 0; + jobject jFXA; + int rc; + assert(pAux); + env = pAux->env; + jFXA = s3jni_getFts5ExensionApi(env); + if( !jFXA ) goto error_oom; + jpFts = new_Fts5Context_wrapper(env, pFts); + if(!jpFts) goto error_oom; + rc = udf_args(env, pCx, argc, argv, &jpCx, &jArgv); + if(rc) goto error_oom; + (*env)->CallVoidMethod(env, pAux->jObj, pAux->jmid, + jFXA, jpFts, jpCx, jArgv); + IFTHREW{ + EXCEPTION_CLEAR; + udf_report_exception(pCx, pAux->zFuncName, "xFunction"); + } + UNREF_L(jpFts); + UNREF_L(jpCx); + UNREF_L(jArgv); + return; +error_oom: + assert( !jArgv ); + assert( !jpCx ); + UNREF_L(jpFts); + sqlite3_result_error_nomem(pCx); + return; +} + +JDECLFtsApi(jint,xCreateFunction)(JENV_JSELF, jstring jName, jobject jFunc){ + fts5_api * const pApi = PtrGet_fts5_api(jSelf); + int rc; + char const * zName; + Fts5JniAux * pAux; + assert(pApi); + zName = JSTR_TOC(jName); + if(!zName) return SQLITE_NOMEM; + pAux = Fts5JniAux_alloc(env, jFunc); + if( pAux ){ + rc = pApi->xCreateFunction(pApi, zName, pAux, + s3jni_fts5_extension_function, + Fts5JniAux_xDestroy); + }else{ + rc = SQLITE_NOMEM; + } + if( 0==rc ){ + pAux->zFuncName = sqlite3_mprintf("%s", zName); + /* OOM here is non-fatal. Ignore it. */ + } + JSTR_RELEASE(jName, zName); + return (jint)rc; +} + + typedef struct s3jni_fts5AuxData s3jni_fts5AuxData; struct s3jni_fts5AuxData { JNIEnv *env; @@ -3022,10 +3167,7 @@ JDECLFtsXA(int,xSetAuxdata)(JENV_JSELF,jobject jCtx, jobject jAux){ } /** - xToken() imp for xTokenize(). - - TODO: hold on to the byte array and avoid initializing - it if passed the same (z,nZ) as a previous call. + xToken() impl for xTokenize(). */ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, int nZ, int iStart, int iEnd){ @@ -3052,12 +3194,16 @@ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, return rc; } -JDECLFtsXA(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, - jobject jCallback){ +/** + Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() +*/ +static jint s3jni_fts5_xTokenize(JENV_JSELF, const char *zClassName, + jint tokFlags, jobject jFcx, + jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; JNIEnvCacheLine * const jc = S3Global_env_cache(env); struct s3jni_xQueryPhraseState s; - int rc; + int rc = 0; jbyte * const pText = JBA_TOC(jbaText); jsize nText = (*env)->GetArrayLength(env, jbaText); jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; @@ -3082,9 +3228,18 @@ JDECLFtsXA(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, s.tok.jba = REF_L(jbaText); s.tok.zPrev = (const char *)pText; s.tok.nPrev = (int)nText; - rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), - (const char *)pText, (int)nText, - &s, s3jni_xTokenize_xToken); + if( zClassName == S3ClassNames.Fts5ExtensionApi ){ + rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), + (const char *)pText, (int)nText, + &s, s3jni_xTokenize_xToken); + }else if( zClassName == S3ClassNames.fts5_tokenizer ){ + fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); + rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, + (const char *)pText, (int)nText, + s3jni_xTokenize_xToken); + }else{ + (*env)->FatalError(env, "This cannot happen. Maintenance required."); + } if(s.tok.jba){ assert( s.tok.zPrev ); UNREF_L(s.tok.jba); @@ -3093,6 +3248,18 @@ JDECLFtsXA(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, return (jint)rc; } +JDECLFtsXA(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, + jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3ClassNames.Fts5ExtensionApi, + 0, jFcx, jbaText, jCallback); +} + +JDECLFtsTok(jint,xTokenize)(JENV_JSELF,jobject jFcx, jint tokFlags, + jbyteArray jbaText, jobject jCallback){ + return s3jni_fts5_xTokenize(env, jSelf, S3ClassNames.Fts5Tokenizer, + tokFlags, jFcx, jbaText, jCallback); +} + #endif /* SQLITE_ENABLE_FTS5 */ //////////////////////////////////////////////////////////////////////// diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 9f842c8aff..3baf8bef33 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1771,7 +1771,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xTokenize - * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5ExtensionApi/xTokenizeCallback;)I + * Signature: (Lorg/sqlite/jni/Fts5Context;[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize (JNIEnv *, jobject, jobject, jbyteArray, jobject); @@ -1797,6 +1797,35 @@ extern "C" { JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb (JNIEnv *, jclass, jobject); +/* + * Class: org_sqlite_jni_fts5_api + * Method: xCreateFunction + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/fts5_api/fts5_extension_function;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction + (JNIEnv *, jobject, jstring, jobject); + +#ifdef __cplusplus +} +#endif +#endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_fts5_tokenizer */ + +#ifndef _Included_org_sqlite_jni_fts5_tokenizer +#define _Included_org_sqlite_jni_fts5_tokenizer +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_fts5_tokenizer + * Method: xTokenize + * Signature: (Lorg/sqlite/jni/Fts5Tokenizer;I[BLorg/sqlite/jni/Fts5/xTokenizeCallback;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize + (JNIEnv *, jobject, jobject, jint, jbyteArray, jobject); + #ifdef __cplusplus } #endif diff --git a/ext/jni/src/org/sqlite/jni/Fts5.java b/ext/jni/src/org/sqlite/jni/Fts5.java new file mode 100644 index 0000000000..102cf575a8 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Fts5.java @@ -0,0 +1,38 @@ +/* +** 2023-08-05 +** +** 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; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_api*) 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 final class Fts5 { + /* Not used */ + private Fts5(){} + + //! Callback type for use with xTokenize() variants + public static interface xTokenizeCallback { + int xToken(int tFlags, byte txt[], int iStart, int iEnd); + } + + public static final int FTS5_TOKENIZE_QUERY = 0x0001; + public static final int FTS5_TOKENIZE_PREFIX = 0x0002; + public static final int FTS5_TOKENIZE_DOCUMENT = 0x0004; + public static final int FTS5_TOKENIZE_AUX = 0x0008; + public static final int FTS5_TOKEN_COLOCATED = 0x0001; +} diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index b7ef3b3883..328ed4c1d3 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -27,10 +27,6 @@ public final class Fts5ExtensionApi extends NativePointerHolder { + //! Only called from JNI. + private Fts5Tokenizer(){} +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 03401dab75..bcaa96a100 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -288,11 +288,6 @@ public final class SQLite3Jni { int eTextRep, @NotNull Collation col); - //Potential TODO, if we can sensibly map the lower-level bits to Java: - //public static native int sqlite3_create_fts5_function(@NotNull sqlite3 db, - // @NotNull String functionName, - // @NotNull Fts5Function func); - /** The Java counterpart to the C-native sqlite3_create_function(), sqlite3_create_function_v2(), and diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 5dd6971398..1e1dbabc50 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -83,7 +83,8 @@ public class Tester1 { affirm(0 == rc); pos = oTail.getValue(); affirm(0 != stmt.getNativePointer()); - rc = sqlite3_step(stmt); + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + } sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer()); if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 3b17c2a49b..3b529e70cc 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -27,7 +27,45 @@ public class TesterFts5 { fts5_api fApi = fts5_api.getInstanceForDb(db); affirm( fApi != null ); affirm( fApi == fts5_api.getInstanceForDb(db) /* singleton per db */ ); + + execSql(db, new String[] { + "CREATE VIRTUAL TABLE ft USING fts5(a, b);", + "INSERT INTO ft(rowid, a, b) VALUES(1, 'X Y', 'Y Z');", + "INSERT INTO ft(rowid, a, b) VALUES(2, 'A Z', 'Y Y');" + }); + + ValueHolder xDestroyCalled = new ValueHolder<>(false); + ValueHolder xFuncCount = new ValueHolder<>(0); + fts5_api.fts5_extension_function func = new fts5_api.fts5_extension_function(){ + + public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]){ + int nCols = ext.xColumnCount(fCx); + affirm( 2 == nCols ); + if(false){ + OutputPointer.String op = new OutputPointer.String(); + for(int i = 0; i < nCols; ++i ){ + int rc = ext.xColumnText(fCx, i, op); + affirm( 0 == rc ); + outln("xFunction col "+i+": "+op.getValue()); + } + } + ++xFuncCount.value; + } + public void xDestroy(){ + xDestroyCalled.value = true; + } + }; + + int rc = fApi.xCreateFunction("myaux", func); + affirm( 0==rc ); + + affirm( 0==xFuncCount.value ); + execSql(db, "select myaux(ft,a,b) from ft;"); + affirm( 2==xFuncCount.value ); + affirm( !xDestroyCalled.value ); sqlite3_close_v2(db); + affirm( xDestroyCalled.value ); } public TesterFts5(){ diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index a4cff7d176..6f48859769 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -1,5 +1,5 @@ /* -** 2023-08-04 +** 2023-08-05 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -22,12 +22,57 @@ package org.sqlite.jni; via JNI. */ public final class fts5_api extends NativePointerHolder { - /* Only invoked by JNI */ + /* Only invoked from JNI */ private fts5_api(){} + public final int iVersion = 2; /** Returns the fts5_api instance associated with the given db, or null if something goes horribly wrong. */ public static native fts5_api getInstanceForDb(@NotNull sqlite3 db); + + public static abstract class fts5_extension_function { + public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]); + //! Optionally override + public void xDestroy(){} + } + + // int (*xCreateTokenizer)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_tokenizer *pTokenizer, + // void (*xDestroy)(void*) + // ); + + // /* Find an existing tokenizer */ + // int (*xFindTokenizer)( + // fts5_api *pApi, + // const char *zName, + // void **ppContext, + // fts5_tokenizer *pTokenizer + // ); + + // /* Create a new auxiliary function */ + // int (*xCreateFunction)( + // fts5_api *pApi, + // const char *zName, + // void *pContext, + // fts5_extension_function xFunction, + // void (*xDestroy)(void*) + // ); + + public native int xCreateFunction(@NotNull String name, + @NotNull fts5_extension_function xFunction); + + // typedef void (*fts5_extension_function)( + // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + // Fts5Context *pFts, /* First arg to pass to pApi functions */ + // sqlite3_context *pCtx, /* Context for returning result/error */ + // int nVal, /* Number of values in apVal[] array */ + // sqlite3_value **apVal /* Array of trailing arguments */ + // ); + } diff --git a/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java new file mode 100644 index 0000000000..097a0cc055 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_tokenizer.java @@ -0,0 +1,49 @@ +/* +** 2023-08-05 +** +** 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; + +/** + INCOMPLETE AND COMPLETELY UNTESTED. + + A wrapper for communicating C-level (fts5_tokenizer*) 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 final class fts5_tokenizer extends NativePointerHolder { + /* Only invoked by JNI */ + private fts5_tokenizer(){} + + // int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + // void (*xDelete)(Fts5Tokenizer*); + + public native int xTokenize(@NotNull Fts5Tokenizer t, int tokFlags, + @NotNull byte pText[], + @NotNull Fts5.xTokenizeCallback callback); + + + // int (*xTokenize)(Fts5Tokenizer*, + // void *pCtx, + // int flags, /* Mask of FTS5_TOKENIZE_* flags */ + // const char *pText, int nText, + // int (*xToken)( + // void *pCtx, /* Copy of 2nd argument to xTokenize() */ + // int tflags, /* Mask of FTS5_TOKEN_* flags */ + // const char *pToken, /* Pointer to buffer containing token */ + // int nToken, /* Size of token in bytes */ + // int iStart, /* Byte offset of token within input text */ + // int iEnd /* Byte offset of end of token within input text */ + // ) + // ); +} diff --git a/manifest b/manifest index 61447c603c..f5e81511fe 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Start\sadding\sfts5_api\sto\sJNI. -D 2023-08-05T01:28:30.501 +C Bind\sfts5_api::xCreateFunction()\sto\sJNI\sand\sdemonstrate\sit\swith\sa\stest. +D 2023-08-05T04:23:27.613 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,30 +230,33 @@ 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 9d916736e5af011664a38d27296692e237afaed41b0843f4de4e1a6136df18d3 +F ext/jni/GNUmakefile 70302fb66d4798b8341be0d702d48acc385eb68cfcf6c68d780e1d5fc218f2ff F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 8c62ed298ccbe46f1b59a1ce9957a75ad8426c5dd065168316d9d2e97e54988b -F ext/jni/src/c/sqlite3-jni.h 2e6450c923fe6a9c7246930d9518c1d7e0008d9cb5143868e8bd4a159fc88901 +F ext/jni/src/c/sqlite3-jni.c 71a03f5348cf5b7be149d46dfe3b0210660e819383d3ba25e0733df572cf0bdc +F ext/jni/src/c/sqlite3-jni.h 526531f90d51a27e808f0758a3965b79bf92c2dd06c1fbcd3f8c37378bba7afd F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a +F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 8922069cf785492b18b15b72c29557de4f10f6462c3c560d1b4827f731f46c3f +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java c2180031e76ba3079be33ef5cae7e3e383f12208845cfc4e0107a00ea82df151 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 +F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee F ext/jni/src/org/sqlite/jni/OutputPointer.java d37636dd3b82097792dae9c8c255b135153845407cdbc6689f15c475850d6c93 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 78496a02c7cc65a2238f54e935af070acf4e2dbef95d7cc1ff46938c440848a4 -F ext/jni/src/org/sqlite/jni/Tester1.java ef715de2ad23ec9b982122c9e1f0dfe689d9d0d7ac6709dab2ad710811bfa50b -F ext/jni/src/org/sqlite/jni/TesterFts5.java 0b1ab9f3675a593213b0f12fabe4fbe6fd16ed994d41a1f4150e760e087099f6 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1c470a8cdb5c61218304eb76b1188e98e562b105eac816557ef512e1b48fa55a +F ext/jni/src/org/sqlite/jni/Tester1.java aaf6cc2c7e01e78eb208f14afa3977862eaae7dd13040acbd302544ae50c21c3 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 0f42841c230992208a07a04b51ea39bfa719d80a763575fa511574a7409870c1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 6ceb87a8aea27727ff5c40abf79aa2f391e9408eeeea921b7ca02c837979ca84 +F ext/jni/src/org/sqlite/jni/fts5_api.java 794bc2bb5850333f0a4e9df557102747b6cb6064b74ecd74dcdbd0d9e0c86eb4 +F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 72a0698aeb50a183ad146cd29ee04952abb8c36021f6122656aa5ec20469f6f7 @@ -2077,8 +2080,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 23383c1dfd240ce47f504dd5c3402c9a31f166fbde5bb72d91309a5655074b33 -R 1f7e3cb1d5a03c9cd76bc5a8ab1fdcba +P 14d18fe983c83412d72fd2005a45a2b8c48d347b7bbf8ef9630ae460cff85c32 +R df417fa0aa6fbe10d9f6667baf8e145c U stephan -Z 82182ddc1d375495dd36c655052d869d +Z 194ff6113095adcb792e4a48e3cee001 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 32314a4148..3d2949e01a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -14d18fe983c83412d72fd2005a45a2b8c48d347b7bbf8ef9630ae460cff85c32 \ No newline at end of file +c653bf16cbdccae05ab14059b140191afd5c17740fb78d756d8822986e54b17c \ No newline at end of file From 4f07be39b74266ca6e4f52116e2dd9ea0783939e Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 04:30:31 +0000 Subject: [PATCH 052/148] Add missing Fts5PhraseIter.java to the build. FossilOrigin-Name: 96281ad0d5b2f020622c4f85f8694886e6a29fb43e1fbeb2a346ed2e94f109fb --- ext/jni/GNUmakefile | 1 + ext/jni/src/org/sqlite/jni/TesterFts5.java | 1 - manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 1440bda5f0..4692d4834b 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -71,6 +71,7 @@ ifeq (1,$(enable.fts5)) Fts5Context.java \ Fts5ExtensionApi.java \ Fts5Function.java \ + Fts5PhraseIter.java \ Fts5Tokenizer.java \ TesterFts5.java \ ) diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 3b529e70cc..1bca5877ee 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -37,7 +37,6 @@ public class TesterFts5 { ValueHolder xDestroyCalled = new ValueHolder<>(false); ValueHolder xFuncCount = new ValueHolder<>(0); fts5_api.fts5_extension_function func = new fts5_api.fts5_extension_function(){ - public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, sqlite3_context pCx, sqlite3_value argv[]){ int nCols = ext.xColumnCount(fCx); diff --git a/manifest b/manifest index f5e81511fe..e0927b1bd7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\sfts5_api::xCreateFunction()\sto\sJNI\sand\sdemonstrate\sit\swith\sa\stest. -D 2023-08-05T04:23:27.613 +C Add\smissing\sFts5PhraseIter.java\sto\sthe\sbuild. +D 2023-08-05T04:30:31.709 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,7 @@ 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 70302fb66d4798b8341be0d702d48acc385eb68cfcf6c68d780e1d5fc218f2ff +F ext/jni/GNUmakefile a875d018a336ae47803cc5ca2768399beaf7b164734612ae9318f56256fc9779 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf F ext/jni/src/c/sqlite3-jni.c 71a03f5348cf5b7be149d46dfe3b0210660e819383d3ba25e0733df572cf0bdc F ext/jni/src/c/sqlite3-jni.h 526531f90d51a27e808f0758a3965b79bf92c2dd06c1fbcd3f8c37378bba7afd @@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1c470a8cdb5c61218304eb76b1188e98e562b105eac816557ef512e1b48fa55a F ext/jni/src/org/sqlite/jni/Tester1.java aaf6cc2c7e01e78eb208f14afa3977862eaae7dd13040acbd302544ae50c21c3 -F ext/jni/src/org/sqlite/jni/TesterFts5.java 0f42841c230992208a07a04b51ea39bfa719d80a763575fa511574a7409870c1 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 9d904ff312eb63b516311c846a2d1aee52e4e2d0b2e3520704624fefb8a94480 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -2080,8 +2080,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 14d18fe983c83412d72fd2005a45a2b8c48d347b7bbf8ef9630ae460cff85c32 -R df417fa0aa6fbe10d9f6667baf8e145c +P c653bf16cbdccae05ab14059b140191afd5c17740fb78d756d8822986e54b17c +R 3768b7bcb33c3430a4b5a25821d24a54 U stephan -Z 194ff6113095adcb792e4a48e3cee001 +Z 400a35d64a4cd5e9266ab634c954577a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3d2949e01a..29afa1b6a1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c653bf16cbdccae05ab14059b140191afd5c17740fb78d756d8822986e54b17c \ No newline at end of file +96281ad0d5b2f020622c4f85f8694886e6a29fb43e1fbeb2a346ed2e94f109fb \ No newline at end of file From 695d0b698a61620be5e92405e57091617a549fa2 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 11:16:54 +0000 Subject: [PATCH 053/148] Bind Fts5ExtensionApi::xUserData() to JNI and extend xCreateFunction() to accept that argument. In test code, use assert() instead of exceptions if assert() is enabled so that test failures (exceptions) thrown via callbacks do not get suppressed (which they otherwise necessarily are to avoid crashing the host app). FossilOrigin-Name: e43837377696e468cd31cf71585fe235ffe67a9f4d3b036c5f9d0cb7141d0f57 --- ext/jni/src/c/sqlite3-jni.c | 21 +++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 12 ++++++++-- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 8 +++---- ext/jni/src/org/sqlite/jni/Tester1.java | 7 ++++-- ext/jni/src/org/sqlite/jni/TesterFts5.java | 4 +++- ext/jni/src/org/sqlite/jni/fts5_api.java | 19 ++++++++++------ manifest | 22 +++++++++---------- manifest.uuid | 2 +- 8 files changed, 63 insertions(+), 32 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index c2397921b2..9e65baafb2 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2677,6 +2677,11 @@ typedef struct { JNIEnv * env; /* env registered from */; jobject jObj /* functor instance */; jclass klazz /* jObj's class */; + jobject jUserData /* 2nd arg to JNI binding of + xCreateFunction(), ostensibly the 3rd arg + to the lib-level xCreateFunction(), except + that we necessarily use that slot for a + Fts5JniAux instance. */; char * zFuncName /* Only for error reporting and debug logging */; jmethodID jmid /* callback member's method ID */; } Fts5JniAux; @@ -2688,6 +2693,7 @@ static void Fts5JniAux_free(Fts5JniAux * const s){ s3jni_call_xDestroy(env, s->jObj, s->klazz); UNREF_G(s->jObj); UNREF_G(s->klazz); + UNREF_G(s->jUserData); } sqlite3_free(s->zFuncName); sqlite3_free(s); @@ -2879,7 +2885,8 @@ error_oom: return; } -JDECLFtsApi(jint,xCreateFunction)(JENV_JSELF, jstring jName, jobject jFunc){ +JDECLFtsApi(jint,xCreateFunction)(JENV_JSELF, jstring jName, + jobject jUserData, jobject jFunc){ fts5_api * const pApi = PtrGet_fts5_api(jSelf); int rc; char const * zName; @@ -2896,8 +2903,9 @@ JDECLFtsApi(jint,xCreateFunction)(JENV_JSELF, jstring jName, jobject jFunc){ rc = SQLITE_NOMEM; } if( 0==rc ){ - pAux->zFuncName = sqlite3_mprintf("%s", zName); - /* OOM here is non-fatal. Ignore it. */ + pAux->jUserData = jUserData ? REF_G(jUserData) : 0; + pAux->zFuncName = sqlite3_mprintf("%s", zName) + /* OOM here is non-fatal. Ignore it. */; } JSTR_RELEASE(jName, zName); return (jint)rc; @@ -3261,6 +3269,13 @@ JDECLFtsTok(jint,xTokenize)(JENV_JSELF,jobject jFcx, jint tokFlags, } +JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ + Fts5ExtDecl; + Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx)); + return pAux ? pAux->jUserData : 0; +} + + #endif /* SQLITE_ENABLE_FTS5 */ //////////////////////////////////////////////////////////////////////// // End of the main API bindings. What follows are internal utilities. diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 3baf8bef33..89e723527b 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1776,6 +1776,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xSetAuxdata JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xTokenize (JNIEnv *, jobject, jobject, jbyteArray, jobject); +/* + * Class: org_sqlite_jni_Fts5ExtensionApi + * Method: xUserData + * Signature: (Lorg/sqlite/jni/Fts5Context;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xUserData + (JNIEnv *, jobject, jobject); + #ifdef __cplusplus } #endif @@ -1800,10 +1808,10 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb /* * Class: org_sqlite_jni_fts5_api * Method: xCreateFunction - * Signature: (Ljava/lang/String;Lorg/sqlite/jni/fts5_api/fts5_extension_function;)I + * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_api/fts5_extension_function;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction - (JNIEnv *, jobject, jstring, jobject); + (JNIEnv *, jobject, jstring, jobject, jobject); #ifdef __cplusplus } diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 328ed4c1d3..d4e47e714b 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -90,9 +90,7 @@ public final class Fts5ExtensionApi extends NativePointerHolder xDestroyCalled = new ValueHolder<>(false); ValueHolder xFuncCount = new ValueHolder<>(0); fts5_api.fts5_extension_function func = new fts5_api.fts5_extension_function(){ @@ -41,6 +42,7 @@ public class TesterFts5 { sqlite3_context pCx, sqlite3_value argv[]){ int nCols = ext.xColumnCount(fCx); affirm( 2 == nCols ); + affirm( ext.xUserData(fCx) == pUserData ); if(false){ OutputPointer.String op = new OutputPointer.String(); for(int i = 0; i < nCols; ++i ){ @@ -56,7 +58,7 @@ public class TesterFts5 { } }; - int rc = fApi.xCreateFunction("myaux", func); + int rc = fApi.xCreateFunction("myaux", pUserData, func); affirm( 0==rc ); affirm( 0==xFuncCount.value ); diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index 6f48859769..9bbaf1be7e 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -33,6 +33,13 @@ public final class fts5_api extends NativePointerHolder { public static native fts5_api getInstanceForDb(@NotNull sqlite3 db); public static abstract class fts5_extension_function { + // typedef void (*fts5_extension_function)( + // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + // Fts5Context *pFts, /* First arg to pass to pApi functions */ + // sqlite3_context *pCtx, /* Context for returning result/error */ + // int nVal, /* Number of values in apVal[] array */ + // sqlite3_value **apVal /* Array of trailing arguments */ + // ); public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, sqlite3_context pCx, sqlite3_value argv[]); //! Optionally override @@ -65,14 +72,12 @@ public final class fts5_api extends NativePointerHolder { // ); public native int xCreateFunction(@NotNull String name, + @Nullable Object userData, @NotNull fts5_extension_function xFunction); - // typedef void (*fts5_extension_function)( - // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ - // Fts5Context *pFts, /* First arg to pass to pApi functions */ - // sqlite3_context *pCtx, /* Context for returning result/error */ - // int nVal, /* Number of values in apVal[] array */ - // sqlite3_value **apVal /* Array of trailing arguments */ - // ); + public int xCreateFunction(@NotNull String name, + @NotNull fts5_extension_function xFunction){ + return xCreateFunction(name, null, xFunction); + } } diff --git a/manifest b/manifest index e0927b1bd7..b868bf4c51 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\smissing\sFts5PhraseIter.java\sto\sthe\sbuild. -D 2023-08-05T04:30:31.709 +C Bind\sFts5ExtensionApi::xUserData()\sto\sJNI\sand\sextend\sxCreateFunction()\sto\saccept\sthat\sargument.\sIn\stest\scode,\suse\sassert()\sinstead\sof\sexceptions\sif\sassert()\sis\senabled\sso\sthat\stest\sfailures\s(exceptions)\sthrown\svia\scallbacks\sdo\snot\sget\ssuppressed\s(which\sthey\sotherwise\snecessarily\sare\sto\savoid\scrashing\sthe\shost\sapp). +D 2023-08-05T11:16:54.971 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,15 +232,15 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile a875d018a336ae47803cc5ca2768399beaf7b164734612ae9318f56256fc9779 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 71a03f5348cf5b7be149d46dfe3b0210660e819383d3ba25e0733df572cf0bdc -F ext/jni/src/c/sqlite3-jni.h 526531f90d51a27e808f0758a3965b79bf92c2dd06c1fbcd3f8c37378bba7afd +F ext/jni/src/c/sqlite3-jni.c db90d821b0129b95ec804bbeab3e223eea34d307a042eab54cdaaeae9e36ec02 +F ext/jni/src/c/sqlite3-jni.h 7bc36622b63d858b06441b19a2f51be9fc1cf2f8177eb28cf5888c4ab6bd930d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java c2180031e76ba3079be33ef5cae7e3e383f12208845cfc4e0107a00ea82df151 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java eab47de28f43ea5deabf9f3da863067c99f96713421bfad7bebb24bcacef4d1c F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 @@ -250,12 +250,12 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1c470a8cdb5c61218304eb76b1188e98e562b105eac816557ef512e1b48fa55a -F ext/jni/src/org/sqlite/jni/Tester1.java aaf6cc2c7e01e78eb208f14afa3977862eaae7dd13040acbd302544ae50c21c3 -F ext/jni/src/org/sqlite/jni/TesterFts5.java 9d904ff312eb63b516311c846a2d1aee52e4e2d0b2e3520704624fefb8a94480 +F ext/jni/src/org/sqlite/jni/Tester1.java e094dca4c2760dba5cd169906f19a01fb7509a318d47fa76de537a6e610c1c47 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 52f36beeb8cd61f31d3fb001bef88befd0c7ce55ed0d94476f9bdfe0dadd46cc F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 794bc2bb5850333f0a4e9df557102747b6cb6064b74ecd74dcdbd0d9e0c86eb4 +F ext/jni/src/org/sqlite/jni/fts5_api.java 97c693d095bfc826c6a2e2906a1fbf53bcbb0aba7798e1135d7957d17d4ad3d4 F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 @@ -2080,8 +2080,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c653bf16cbdccae05ab14059b140191afd5c17740fb78d756d8822986e54b17c -R 3768b7bcb33c3430a4b5a25821d24a54 +P 96281ad0d5b2f020622c4f85f8694886e6a29fb43e1fbeb2a346ed2e94f109fb +R f5fddb77ac440b71910871c77b4331be U stephan -Z 400a35d64a4cd5e9266ab634c954577a +Z 93c24d40c86efbed9f0183e7bb02737a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 29afa1b6a1..a02a8a1032 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -96281ad0d5b2f020622c4f85f8694886e6a29fb43e1fbeb2a346ed2e94f109fb \ No newline at end of file +e43837377696e468cd31cf71585fe235ffe67a9f4d3b036c5f9d0cb7141d0f57 \ No newline at end of file From fdeb2c81212bb0675085a502f55a2716bb6523ad Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 12:48:33 +0000 Subject: [PATCH 054/148] JNI internal refactoring and cleanups. FossilOrigin-Name: 7397973a2f191d75b149cf73a6d7ee1798820c1cd37d83af14e565067ede1b04 --- ext/jni/GNUmakefile | 1 + ext/jni/src/c/sqlite3-jni.c | 174 +++++++++++------- ext/jni/src/c/sqlite3-jni.h | 2 +- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 4 + ext/jni/src/org/sqlite/jni/OutputPointer.java | 16 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 5 + ext/jni/src/org/sqlite/jni/Tester1.java | 17 +- ext/jni/src/org/sqlite/jni/TesterFts5.java | 9 +- ext/jni/src/org/sqlite/jni/fts5_api.java | 14 -- .../sqlite/jni/fts5_extension_function.java | 37 ++++ manifest | 29 +-- manifest.uuid | 2 +- 12 files changed, 203 insertions(+), 107 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/fts5_extension_function.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 4692d4834b..d3b376ba0e 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -66,6 +66,7 @@ JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ ifeq (1,$(enable.fts5)) JAVA_FILES += $(patsubst %,$(dir.src.jni)/%,\ fts5_api.java \ + fts5_extension_function.java \ fts5_tokenizer.java \ Fts5.java \ Fts5Context.java \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 9e65baafb2..67682e130f 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -320,12 +320,18 @@ enum { */ typedef struct NphCacheLine NphCacheLine; struct NphCacheLine { - const char * zClassName /* "full/class/Name". Must be a static string - from the S3ClassNames struct. */; - jclass klazz /* global ref to concrete NativePointerHolder class */; - jmethodID midCtor /* klazz's constructor */; - jfieldID fidValue /* NativePointerHolder.nativePointer and OutputPointer.X.value */; - jfieldID fidSetAgg /* sqlite3_context::aggregateContext */; + const char * zClassName /* "full/class/Name". Must be a static + string pointer from the S3ClassNames + struct. */; + jclass klazz /* global ref to the concrete + NativePointerHolder subclass represented by + zClassName */; + jmethodID midCtor /* klazz's no-arg constructor. Used by + new_NativePointerHolder_object(). */; + jfieldID fidValue /* NativePointerHolder.nativePointer and + OutputPointer.X.value */; + jfieldID fidSetAgg /* sqlite3_context::aggregateContext. Used only + by the sqlite3_context binding. */; }; /** @@ -959,11 +965,18 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, } /** - Common init for setOutputInt32() and friends. + Common init for setOutputInt32() and friends. zClassName must be a + pointer from S3ClassNames. jOut must be an instance of that + class. Fetches the jfieldID for jOut's [value] property, which must + be of the type represented by the JNI type signature zTypeSig, and + stores it in pFieldId. Fails fatally if the property is not found, + as that presents a serious internal misuse. + + Property lookups are cached on a per-class basis. */ static void setupOutputPointer(JNIEnv * env, const char *zClassName, const char *zTypeSig, - jobject jOut, jfieldID * pSetter){ + jobject jOut, jfieldID * pFieldId){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); @@ -978,10 +991,11 @@ static void setupOutputPointer(JNIEnv * env, const char *zClassName, cacheLine->fidValue = setter; } } - *pSetter = setter; + *pFieldId = setter; } -/* Sets a native int32 value in OutputPointer.Int32 object jOut. */ +/* Sets the value property of the OutputPointer.Int32 jOut object + to v. */ static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_Int32, "I", jOut, &setter); @@ -990,13 +1004,16 @@ static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ } #ifdef SQLITE_ENABLE_FTS5 -/* Sets a native int64 value in OutputPointer.Int64 object jOut. */ +/* Sets the value property of the OutputPointer.Int64 jOut object + to v. */ static void setOutputInt64(JNIEnv * env, jobject jOut, jlong v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_Int64, "J", jOut, &setter); (*env)->SetLongField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } +/* Sets the value property of the OutputPointer.ByteArray jOut object + to v. */ static void setOutputByteArray(JNIEnv * env, jobject jOut, jbyteArray v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_ByteArray, "[B", @@ -1005,7 +1022,8 @@ static void setOutputByteArray(JNIEnv * env, jobject jOut, jbyteArray v){ EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); } #if 0 -/* Sets a String value in OutputPointer.String object jOut. */ +/* Sets the value property of the OutputPointer.String jOut object + to v. */ static void setOutputString(JNIEnv * env, jobject jOut, jstring v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_String, "Ljava/lang/String", @@ -1013,6 +1031,7 @@ static void setOutputString(JNIEnv * env, jobject jOut, jstring v){ (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); } +//! Bad: uses MUTF-8 encoding. static void setOutputString2(JNIEnv * env, jobject jOut, const char * zStr){ jstring const jStr = (*env)->NewStringUTF(env, zStr); if(jStr){ @@ -1176,15 +1195,15 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zC return rv; } -static jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ +static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_value, sv); } -static jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ +static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_context, sv); } -static jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ +static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_stmt, sv); } @@ -1714,7 +1733,7 @@ JDECL(jlong,1column_1int64)(JENV_JSELF, jobject jpStmt, Java String of exactly half that length, returning NULL if !p or (*env)->NewString() fails. */ -static jstring s3jni_text_to_jstring(JNIEnv * const env, const void * const p, int nP){ +static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ return p ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) : NULL; @@ -1737,7 +1756,7 @@ JDECL(jstring,1column_1text)(JENV_JSELF, jobject jpStmt, sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes16(stmt, (int)ndx); const void * const p = sqlite3_column_text16(stmt, (int)ndx); - return s3jni_text_to_jstring(env, p, n); + return s3jni_text16_to_jstring(env, p, n); } JDECL(jbyteArray,1column_1text_1utf8)(JENV_JSELF, jobject jpStmt, @@ -1838,10 +1857,10 @@ JDECL(jstring,1compileoption_1get)(JENV_JSELF, jint n){ } JDECL(jboolean,1compileoption_1used)(JENV_JSELF, jstring name){ - const char *zUtf8 = (*env)->GetStringUTFChars(env, name, NULL); + const char *zUtf8 = JSTR_TOC(name); const jboolean rc = 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; - (*env)->ReleaseStringUTFChars(env, name, zUtf8); + JSTR_RELEASE(name, zUtf8); return rc; } @@ -1899,11 +1918,13 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, else if( UDF_UNKNOWN_TYPE==s->type ){ rc = s3jni_db_error(pDb, SQLITE_MISUSE, "Cannot unambiguously determine function type."); + UDFState_free(s); goto error_cleanup; } zFuncName = JSTR_TOC(jFuncName); if(!zFuncName){ rc = SQLITE_NOMEM; + UDFState_free(s); goto error_cleanup; } if( UDF_WINDOW == s->type ){ @@ -1925,13 +1946,10 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, xFunc, xStep, xFinal, UDFState_finalizer); } if( 0==rc ){ - s->zFuncName = sqlite3_mprintf("%s", zFuncName); - if( !s->zFuncName ){ - rc = SQLITE_NOMEM; - } - } - if( 0!=rc ){ - UDFState_free(s); + s->zFuncName = sqlite3_mprintf("%s", zFuncName) + /* OOM here is non-fatal. Ignore it. Handling it would require + re-calling the appropriate create_function() func with 0 + for all xAbc args so that s would be finalized. */; } error_cleanup: JSTR_RELEASE(jFuncName, zFuncName); @@ -2568,7 +2586,7 @@ JDECL(jstring,1value_1text)(JENV_JSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const n = sqlite3_value_bytes16(sv); const void * const p = sqlite3_value_text16(sv); - return s3jni_text_to_jstring(env, p, n); + return s3jni_text16_to_jstring(env, p, n); } JDECL(jbyteArray,1value_1text_1utf8)(JENV_JSELF, jobject jpSVal){ @@ -2619,6 +2637,14 @@ JDECL(jbyteArray,1value_1text16be)(JENV_JSELF, jobject jpSVal){ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ MARKER(("\nVarious bits of internal info:\n")); + puts("FTS5 is " +#ifdef SQLITE_ENABLE_FTS5 + "available" +#else + "unavailable" +#endif + "." + ); puts("sizeofs:"); #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); @@ -2626,6 +2652,9 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ SO(PerDbStateJni); SO(S3Global); SO(JNIEnvCache); + SO(S3ClassNames); + printf("\t(^^^ %u NativePointerHolder subclasses)\n", + (unsigned)(sizeof(S3ClassNames) / sizeof(const char *))); printf("Cache info:\n"); printf("\tNativePointerHolder cache: %u misses, %u hits\n", S3Global.metrics.nphCacheMisses, @@ -2669,6 +2698,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ #define PtrGet_fts5_tokenizer(OBJ) getNativePointer(env,OBJ,S3ClassNames.fts5_tokenizer) #define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) #define PtrGet_Fts5Tokenizer(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Tokenizer) +#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() /** State for binding Java-side FTS5 auxiliary functions. @@ -2730,12 +2760,11 @@ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ static inline Fts5ExtensionApi const * s3jni_ftsext(void){ return &sFts5Api/*singleton from sqlite3.c*/; } -#define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() -static jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ +static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); } -static jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ +static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ return new_NativePointerHolder_object(env, S3ClassNames.fts5_api, sv); } @@ -3291,41 +3320,53 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ */ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jobject sJni){ + enum JType { + JTYPE_INT, + JTYPE_BOOL + }; typedef struct { const char *zName; + enum JType jtype; int value; - } LimitEntry; - const LimitEntry aLimits[] = { - {"SQLITE_MAX_ALLOCATION_SIZE", SQLITE_MAX_ALLOCATION_SIZE}, - {"SQLITE_LIMIT_LENGTH", SQLITE_LIMIT_LENGTH}, - {"SQLITE_MAX_LENGTH", SQLITE_MAX_LENGTH}, - {"SQLITE_LIMIT_SQL_LENGTH", SQLITE_LIMIT_SQL_LENGTH}, - {"SQLITE_MAX_SQL_LENGTH", SQLITE_MAX_SQL_LENGTH}, - {"SQLITE_LIMIT_COLUMN", SQLITE_LIMIT_COLUMN}, - {"SQLITE_MAX_COLUMN", SQLITE_MAX_COLUMN}, - {"SQLITE_LIMIT_EXPR_DEPTH", SQLITE_LIMIT_EXPR_DEPTH}, - {"SQLITE_MAX_EXPR_DEPTH", SQLITE_MAX_EXPR_DEPTH}, - {"SQLITE_LIMIT_COMPOUND_SELECT", SQLITE_LIMIT_COMPOUND_SELECT}, - {"SQLITE_MAX_COMPOUND_SELECT", SQLITE_MAX_COMPOUND_SELECT}, - {"SQLITE_LIMIT_VDBE_OP", SQLITE_LIMIT_VDBE_OP}, - {"SQLITE_MAX_VDBE_OP", SQLITE_MAX_VDBE_OP}, - {"SQLITE_LIMIT_FUNCTION_ARG", SQLITE_LIMIT_FUNCTION_ARG}, - {"SQLITE_MAX_FUNCTION_ARG", SQLITE_MAX_FUNCTION_ARG}, - {"SQLITE_LIMIT_ATTACHED", SQLITE_LIMIT_ATTACHED}, - {"SQLITE_MAX_ATTACHED", SQLITE_MAX_ATTACHED}, - {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, - {"SQLITE_MAX_LIKE_PATTERN_LENGTH", SQLITE_MAX_LIKE_PATTERN_LENGTH}, - {"SQLITE_LIMIT_VARIABLE_NUMBER", SQLITE_LIMIT_VARIABLE_NUMBER}, - {"SQLITE_MAX_VARIABLE_NUMBER", SQLITE_MAX_VARIABLE_NUMBER}, - {"SQLITE_LIMIT_TRIGGER_DEPTH", SQLITE_LIMIT_TRIGGER_DEPTH}, - {"SQLITE_MAX_TRIGGER_DEPTH", SQLITE_MAX_TRIGGER_DEPTH}, - {"SQLITE_LIMIT_WORKER_THREADS", SQLITE_LIMIT_WORKER_THREADS}, - {"SQLITE_MAX_WORKER_THREADS", SQLITE_MAX_WORKER_THREADS}, + } ConfigFlagEntry; + const ConfigFlagEntry aLimits[] = { + {"SQLITE_ENABLE_FTS5", JTYPE_BOOL, +#ifdef SQLITE_ENABLE_FTS5 + 1 +#else + 0 +#endif + }, + {"SQLITE_MAX_ALLOCATION_SIZE", JTYPE_INT, SQLITE_MAX_ALLOCATION_SIZE}, + {"SQLITE_LIMIT_LENGTH", JTYPE_INT, SQLITE_LIMIT_LENGTH}, + {"SQLITE_MAX_LENGTH", JTYPE_INT, SQLITE_MAX_LENGTH}, + {"SQLITE_LIMIT_SQL_LENGTH", JTYPE_INT, SQLITE_LIMIT_SQL_LENGTH}, + {"SQLITE_MAX_SQL_LENGTH", JTYPE_INT, SQLITE_MAX_SQL_LENGTH}, + {"SQLITE_LIMIT_COLUMN", JTYPE_INT, SQLITE_LIMIT_COLUMN}, + {"SQLITE_MAX_COLUMN", JTYPE_INT, SQLITE_MAX_COLUMN}, + {"SQLITE_LIMIT_EXPR_DEPTH", JTYPE_INT, SQLITE_LIMIT_EXPR_DEPTH}, + {"SQLITE_MAX_EXPR_DEPTH", JTYPE_INT, SQLITE_MAX_EXPR_DEPTH}, + {"SQLITE_LIMIT_COMPOUND_SELECT", JTYPE_INT, SQLITE_LIMIT_COMPOUND_SELECT}, + {"SQLITE_MAX_COMPOUND_SELECT", JTYPE_INT, SQLITE_MAX_COMPOUND_SELECT}, + {"SQLITE_LIMIT_VDBE_OP", JTYPE_INT, SQLITE_LIMIT_VDBE_OP}, + {"SQLITE_MAX_VDBE_OP", JTYPE_INT, SQLITE_MAX_VDBE_OP}, + {"SQLITE_LIMIT_FUNCTION_ARG", JTYPE_INT, SQLITE_LIMIT_FUNCTION_ARG}, + {"SQLITE_MAX_FUNCTION_ARG", JTYPE_INT, SQLITE_MAX_FUNCTION_ARG}, + {"SQLITE_LIMIT_ATTACHED", JTYPE_INT, SQLITE_LIMIT_ATTACHED}, + {"SQLITE_MAX_ATTACHED", JTYPE_INT, SQLITE_MAX_ATTACHED}, + {"SQLITE_LIMIT_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, + {"SQLITE_MAX_LIKE_PATTERN_LENGTH", JTYPE_INT, SQLITE_MAX_LIKE_PATTERN_LENGTH}, + {"SQLITE_LIMIT_VARIABLE_NUMBER", JTYPE_INT, SQLITE_LIMIT_VARIABLE_NUMBER}, + {"SQLITE_MAX_VARIABLE_NUMBER", JTYPE_INT, SQLITE_MAX_VARIABLE_NUMBER}, + {"SQLITE_LIMIT_TRIGGER_DEPTH", JTYPE_INT, SQLITE_LIMIT_TRIGGER_DEPTH}, + {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH}, + {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS}, + {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS}, {0,0} }; jfieldID fieldId; jclass const klazz = (*env)->GetObjectClass(env, sJni); - const LimitEntry * pLimit; + const ConfigFlagEntry * pConfFlag; memset(&S3Global, 0, sizeof(S3Global)); (void)S3Global_env_cache(env); assert( 1 == S3Global.envCache.used ); @@ -3335,12 +3376,21 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jobject sJn (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); } - for( pLimit = &aLimits[0]; pLimit->zName; ++pLimit ){ - fieldId = (*env)->GetStaticFieldID(env, klazz, pLimit->zName, "I"); + for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ + char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; + fieldId = (*env)->GetStaticFieldID(env, klazz, pConfFlag->zName, zSig); EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); - //MARKER(("Setting %s (field=%p) = %d\n", pLimit->zName, fieldId, pLimit->value)); + //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); assert(fieldId); - (*env)->SetStaticIntField(env, klazz, fieldId, (jint)pLimit->value); + switch(pConfFlag->jtype){ + case JTYPE_INT: + (*env)->SetStaticIntField(env, klazz, fieldId, (jint)pConfFlag->value); + break; + case JTYPE_BOOL: + (*env)->SetStaticBooleanField(env, klazz, fieldId, + pConfFlag->value ? JNI_TRUE : JNI_FALSE); + break; + } EXCEPTION_IS_FATAL("Seting a static member of the SQLite3Jni class failed."); } } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 89e723527b..1f0e071d1a 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1808,7 +1808,7 @@ JNIEXPORT jobject JNICALL Java_org_sqlite_jni_fts5_1api_getInstanceForDb /* * Class: org_sqlite_jni_fts5_api * Method: xCreateFunction - * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_api/fts5_extension_function;)I + * Signature: (Ljava/lang/String;Ljava/lang/Object;Lorg/sqlite/jni/fts5_extension_function;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1api_xCreateFunction (JNIEnv *, jobject, jstring, jobject, jobject); diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index d4e47e714b..2667667d97 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -41,6 +41,10 @@ public final class Fts5ExtensionApi extends NativePointerHolder xDestroyCalled = new ValueHolder<>(false); ValueHolder xFuncCount = new ValueHolder<>(0); - fts5_api.fts5_extension_function func = new fts5_api.fts5_extension_function(){ + final fts5_extension_function func = new fts5_extension_function(){ public void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, sqlite3_context pCx, sqlite3_value argv[]){ int nCols = ext.xColumnCount(fCx); affirm( 2 == nCols ); + affirm( nCols == argv.length ); affirm( ext.xUserData(fCx) == pUserData ); - if(false){ + if(true){ OutputPointer.String op = new OutputPointer.String(); for(int i = 0; i < nCols; ++i ){ int rc = ext.xColumnText(fCx, i, op); affirm( 0 == rc ); - outln("xFunction col "+i+": "+op.getValue()); + final String val = op.getValue(); + affirm( val.equals(sqlite3_value_text(argv[i])) ); + //outln("xFunction col "+i+": "+val); } } ++xFuncCount.value; diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index 9bbaf1be7e..53afeb93c6 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -32,20 +32,6 @@ public final class fts5_api extends NativePointerHolder { */ public static native fts5_api getInstanceForDb(@NotNull sqlite3 db); - public static abstract class fts5_extension_function { - // typedef void (*fts5_extension_function)( - // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ - // Fts5Context *pFts, /* First arg to pass to pApi functions */ - // sqlite3_context *pCtx, /* Context for returning result/error */ - // int nVal, /* Number of values in apVal[] array */ - // sqlite3_value **apVal /* Array of trailing arguments */ - // ); - public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, - sqlite3_context pCx, sqlite3_value argv[]); - //! Optionally override - public void xDestroy(){} - } - // int (*xCreateTokenizer)( // fts5_api *pApi, // const char *zName, diff --git a/ext/jni/src/org/sqlite/jni/fts5_extension_function.java b/ext/jni/src/org/sqlite/jni/fts5_extension_function.java new file mode 100644 index 0000000000..0e273119f5 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/fts5_extension_function.java @@ -0,0 +1,37 @@ +/* +** 2023-08-05 +** +** 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; + +/** + JNI-level wrapper for C's fts5_extension_function type. + +*/ +public abstract class fts5_extension_function { + // typedef void (*fts5_extension_function)( + // const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + // Fts5Context *pFts, /* First arg to pass to pApi functions */ + // sqlite3_context *pCtx, /* Context for returning result/error */ + // int nVal, /* Number of values in apVal[] array */ + // sqlite3_value **apVal /* Array of trailing arguments */ + // ); + + /** + The callback implementation, corresponding to the xFunction + argument of C's fts5_api::xCreateFunction(). + */ + public abstract void xFunction(Fts5ExtensionApi ext, Fts5Context fCx, + sqlite3_context pCx, sqlite3_value argv[]); + //! Optionally override + public void xDestroy(){} +} diff --git a/manifest b/manifest index b868bf4c51..def65b06d5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\sFts5ExtensionApi::xUserData()\sto\sJNI\sand\sextend\sxCreateFunction()\sto\saccept\sthat\sargument.\sIn\stest\scode,\suse\sassert()\sinstead\sof\sexceptions\sif\sassert()\sis\senabled\sso\sthat\stest\sfailures\s(exceptions)\sthrown\svia\scallbacks\sdo\snot\sget\ssuppressed\s(which\sthey\sotherwise\snecessarily\sare\sto\savoid\scrashing\sthe\shost\sapp). -D 2023-08-05T11:16:54.971 +C JNI\sinternal\srefactoring\sand\scleanups. +D 2023-08-05T12:48:33.207 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,32 +230,33 @@ 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 a875d018a336ae47803cc5ca2768399beaf7b164734612ae9318f56256fc9779 +F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c db90d821b0129b95ec804bbeab3e223eea34d307a042eab54cdaaeae9e36ec02 -F ext/jni/src/c/sqlite3-jni.h 7bc36622b63d858b06441b19a2f51be9fc1cf2f8177eb28cf5888c4ab6bd930d +F ext/jni/src/c/sqlite3-jni.c 110e133920b469d1ff83ca4c1f23f8b8e6abce40c62b905bedfbdd6d117338a9 +F ext/jni/src/c/sqlite3-jni.h 96561bfb446e4481dea3016e57299e30ad97bca8a0256c04c83a4068681e6abd F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java eab47de28f43ea5deabf9f3da863067c99f96713421bfad7bebb24bcacef4d1c +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 15e524b997bac1449a273c6eb5d6b095ec7f9d0c88a8f8042d50ec66710e6f28 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java d37636dd3b82097792dae9c8c255b135153845407cdbc6689f15c475850d6c93 +F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3be857b4d4622f22ec5f2c28d9b5d6650d F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1c470a8cdb5c61218304eb76b1188e98e562b105eac816557ef512e1b48fa55a -F ext/jni/src/org/sqlite/jni/Tester1.java e094dca4c2760dba5cd169906f19a01fb7509a318d47fa76de537a6e610c1c47 -F ext/jni/src/org/sqlite/jni/TesterFts5.java 52f36beeb8cd61f31d3fb001bef88befd0c7ce55ed0d94476f9bdfe0dadd46cc +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java c98b13c1e3843a42cf9959d094ac301113f35a460cbeef3d9cadacdbaa8afeb8 +F ext/jni/src/org/sqlite/jni/Tester1.java 732d26e858cfe32d664eab805ed8331fcef5cd460b19aa9afac8636f8a92bda3 +F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 97c693d095bfc826c6a2e2906a1fbf53bcbb0aba7798e1135d7957d17d4ad3d4 +F ext/jni/src/org/sqlite/jni/fts5_api.java ae52ff7f963976fabb7e87b0b8cdb3f9d2ba1838e7d3b79b0b4cb526202d4709 +F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 @@ -2080,8 +2081,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 96281ad0d5b2f020622c4f85f8694886e6a29fb43e1fbeb2a346ed2e94f109fb -R f5fddb77ac440b71910871c77b4331be +P e43837377696e468cd31cf71585fe235ffe67a9f4d3b036c5f9d0cb7141d0f57 +R 1c284c5b70026e59635fc8c8a01c3637 U stephan -Z 93c24d40c86efbed9f0183e7bb02737a +Z d433ff069de41673d993c33002d0fb03 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a02a8a1032..e6d0ad93fc 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e43837377696e468cd31cf71585fe235ffe67a9f4d3b036c5f9d0cb7141d0f57 \ No newline at end of file +7397973a2f191d75b149cf73a6d7ee1798820c1cd37d83af14e565067ede1b04 \ No newline at end of file From 27cb7d6a1df72639ac683b682cb3e9288a0cd930 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 19:20:15 +0000 Subject: [PATCH 055/148] In the JNI docs, note that the sizeof SQLITE_TRANSIENT and SQLITE_STATIC should ideally be the same as the platform's pointer size. FossilOrigin-Name: 7d4ac44ec419ed0474bdb9d237b97660cf0d8faba8fe686f6a914d7bc04dfa3b --- ext/jni/src/c/sqlite3-jni.h | 4 ++-- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 8 +++++--- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 1f0e071d1a..17695b4908 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -84,9 +84,9 @@ extern "C" { #undef org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE #define org_sqlite_jni_SQLite3Jni_SQLITE_RECURSIVE 33L #undef org_sqlite_jni_SQLite3Jni_SQLITE_STATIC -#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0L +#define org_sqlite_jni_SQLite3Jni_SQLITE_STATIC 0LL #undef org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1L +#define org_sqlite_jni_SQLite3Jni_SQLITE_TRANSIENT -1LL #undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT #define org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETSTART_INVERT 2L #undef org_sqlite_jni_SQLite3Jni_SQLITE_CHANGESETAPPLY_NOSAVEPOINT diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 2ae89a1cd6..80cbbad391 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -849,9 +849,11 @@ public final class SQLite3Jni { public static final int SQLITE_SAVEPOINT = 32; public static final int SQLITE_RECURSIVE = 33; - // blob finalizers: - public static final int SQLITE_STATIC = 0; - public static final int SQLITE_TRANSIENT = -1; + // blob finalizers: these should, because they are treated as + // special pointer values in C, ideally have the same sizeof() as + // the platform's (void*), but we can't know that size from here. + public static final long SQLITE_STATIC = 0; + public static final long SQLITE_TRANSIENT = -1; // changeset public static final int SQLITE_CHANGESETSTART_INVERT = 2; diff --git a/manifest b/manifest index def65b06d5..c7fb5d16f3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI\sinternal\srefactoring\sand\scleanups. -D 2023-08-05T12:48:33.207 +C In\sthe\sJNI\sdocs,\snote\sthat\sthe\ssizeof\sSQLITE_TRANSIENT\sand\sSQLITE_STATIC\sshould\sideally\sbe\sthe\ssame\sas\sthe\splatform's\spointer\ssize. +D 2023-08-05T19:20:15.499 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,7 +233,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf F ext/jni/src/c/sqlite3-jni.c 110e133920b469d1ff83ca4c1f23f8b8e6abce40c62b905bedfbdd6d117338a9 -F ext/jni/src/c/sqlite3-jni.h 96561bfb446e4481dea3016e57299e30ad97bca8a0256c04c83a4068681e6abd +F ext/jni/src/c/sqlite3-jni.h c3824071f3b7f09149ebc9921c5d0295fe343b31eb7a2c41fb6ff76cd2975f64 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -249,7 +249,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3b F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java c98b13c1e3843a42cf9959d094ac301113f35a460cbeef3d9cadacdbaa8afeb8 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 0bdd63552ad5a8d8aa4429008c5da4607720f913ee995b622f8c8063ed2baf09 F ext/jni/src/org/sqlite/jni/Tester1.java 732d26e858cfe32d664eab805ed8331fcef5cd460b19aa9afac8636f8a92bda3 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2081,8 +2081,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P e43837377696e468cd31cf71585fe235ffe67a9f4d3b036c5f9d0cb7141d0f57 -R 1c284c5b70026e59635fc8c8a01c3637 +P 7397973a2f191d75b149cf73a6d7ee1798820c1cd37d83af14e565067ede1b04 +R c054a9f4974b02bb5146402583a90c21 U stephan -Z d433ff069de41673d993c33002d0fb03 +Z 0e23b6ff523f68c333f5ec7c4d0652b3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e6d0ad93fc..af0a6667d3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7397973a2f191d75b149cf73a6d7ee1798820c1cd37d83af14e565067ede1b04 \ No newline at end of file +7d4ac44ec419ed0474bdb9d237b97660cf0d8faba8fe686f6a914d7bc04dfa3b \ No newline at end of file From 67214a9594adf2d0fd86c77eae827be7d63808fe Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 20:19:45 +0000 Subject: [PATCH 056/148] Add SQLite3Jni.uncacheJniEnv(), a way for Java threads to clear their thread-specific cached state from the JNI bindings when they're about to terminate (or are otherwise done using the library). FossilOrigin-Name: 7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f --- ext/jni/src/c/sqlite3-jni.c | 63 ++++++++++++++++++---- ext/jni/src/c/sqlite3-jni.h | 12 ++++- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 31 ++++++++++- ext/jni/src/org/sqlite/jni/Tester1.java | 2 + manifest | 18 +++---- manifest.uuid | 2 +- 6 files changed, 105 insertions(+), 23 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 67682e130f..0c9862d402 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -286,7 +286,7 @@ static const struct { #define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) #define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) #define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) -#define JBA_RELEASE(ARG,VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) +#define JBA_RELEASE(ARG,VAR) if(ARG) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) /* Marker for code which needs(?) to be made thread-safe. */ #define FIXME_THREADING @@ -336,6 +336,14 @@ struct NphCacheLine { /** Cache for per-JNIEnv data. + + Potential TODO: move the jclass entries to global space because, + per https://developer.android.com/training/articles/perf-jni: + + > once you have a valid jclass global reference you can use it from + any attached thread. + + Whereas we cache new refs for each thread. */ typedef struct JNIEnvCacheLine JNIEnvCacheLine; struct JNIEnvCacheLine { @@ -863,7 +871,8 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ return a (sqlite3*) but do not take one as an argument. */ FIXME_THREADING -static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ +static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, + sqlite3 *pDb, int allocIfNeeded){ PerDbStateJni * s = S3Global.perDb.aUsed; if(!jDb){ if(pDb){ @@ -3310,16 +3319,52 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ // End of the main API bindings. What follows are internal utilities. //////////////////////////////////////////////////////////////////////// +/** + Uncaches the current JNIEnv from the S3Global state, clearing any + resources owned by that cache entry and making that slot available + for re-use. It is important that the Java-side decl of this + function be declared as synchronous. +*/ +JNIEXPORT jboolean JNICALL +Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JNIEnv * const env, jclass self){ + struct JNIEnvCacheLine * row = 0; + int i; + for( i = 0; i < JNIEnvCache_SIZE; ++i ){ + row = &S3Global.envCache.lines[i]; + if(row->env == env){ + break; + } + } + if( i==JNIEnvCache_SIZE ){ + //MARKER(("The given JNIEnv is not currently cached.\n")); + return JNI_FALSE; + } + //MARKER(("Uncaching S3Global.envCache entry #%d.\n", i)); + assert(S3Global.envCache.used >= i); + JNIEnvCacheLine_clear(row); + /** + Move all entries down one slot. memmove() would be faster. We'll + eventually turn this cache into a dynamically-allocated linked + list, anyway, so this part will go away. + */ + for( ++i ; i < JNIEnvCache_SIZE; ++i ){ + S3Global.envCache.lines[i-i] = S3Global.envCache.lines[i]; + } + memset(&S3Global.envCache.lines[i], 0, sizeof(JNIEnvCacheLine)); + --S3Global.envCache.used; + return JNI_TRUE; +} + /** Called during static init of the SQLite3Jni class to sync certain compile-time constants to Java-space. - This routine is why we have to #include sqlite3.c instead of - sqlite3.h. + This routine is part of the reason why we have to #include + sqlite3.c instead of sqlite3.h. */ JNIEXPORT void JNICALL -Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jobject sJni){ +Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klazzSjni){ enum JType { JTYPE_INT, JTYPE_BOOL @@ -3365,7 +3410,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jobject sJn {0,0} }; jfieldID fieldId; - jclass const klazz = (*env)->GetObjectClass(env, sJni); + //jclass const klazz = (*env)->GetObjectClass(env, sJni); const ConfigFlagEntry * pConfFlag; memset(&S3Global, 0, sizeof(S3Global)); (void)S3Global_env_cache(env); @@ -3378,16 +3423,16 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jobject sJn for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; - fieldId = (*env)->GetStaticFieldID(env, klazz, pConfFlag->zName, zSig); + fieldId = (*env)->GetStaticFieldID(env, klazzSjni, pConfFlag->zName, zSig); EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); assert(fieldId); switch(pConfFlag->jtype){ case JTYPE_INT: - (*env)->SetStaticIntField(env, klazz, fieldId, (jint)pConfFlag->value); + (*env)->SetStaticIntField(env, klazzSjni, fieldId, (jint)pConfFlag->value); break; case JTYPE_BOOL: - (*env)->SetStaticBooleanField(env, klazz, fieldId, + (*env)->SetStaticBooleanField(env, klazzSjni, fieldId, pConfFlag->value ? JNI_TRUE : JNI_FALSE); break; } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 17695b4908..d05bf77e45 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -758,10 +758,18 @@ extern "C" { /* * Class: org_sqlite_jni_SQLite3Jni * Method: init - * Signature: (Lorg/sqlite/jni/SQLite3Jni;)V + * Signature: (Ljava/lang/Class;)V */ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init - (JNIEnv *, jclass, jobject); + (JNIEnv *, jclass, jclass); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: uncacheJniEnv + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv + (JNIEnv *, jclass); /* * Class: org_sqlite_jni_SQLite3Jni diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 80cbbad391..0320b102fe 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -66,8 +66,32 @@ public final class SQLite3Jni { static { System.loadLibrary("sqlite3-jni"); } + //! Not used private SQLite3Jni(){} - private static native void init(@NotNull SQLite3Jni s); + //! Called from static init code. + private static native void init(@NotNull Class c); + + /** + Each thread which uses the SQLite3 JNI APIs should call + uncacheJniEnv() when it is done with the library - either right + before it terminates or when it is finished using the SQLite API. + This will clean up any cached per-JNIEnv info. Calling into the + library again after that "should" re-initialize the cache on + demand, but that's untested. + + Calling this from the main application thread is not strictly + required but is "polite." Additional threads must call this + before ending or they will leak cache entries in the C memory, + which in turn may keep numerous Java-side global references + active. + + This routine returns false without side effects if the current + JNIEnv is not cached, else returns true, but this information is + primarily for testing of the JNI bindings and is not information + which client-level code should use to make any informed + decisions. +*/ + public static synchronized native boolean uncacheJniEnv(); ////////////////////////////////////////////////////////////////////// // Maintenance reminder: please keep the functions alphabetized. @@ -98,6 +122,9 @@ public final class SQLite3Jni { public static native int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt); + /** A level of indirection required to ensure that the input to the + C-level function of the same name is a NUL-terminated UTF-8 + string. */ private static native int sqlite3_bind_parameter_index(@NotNull sqlite3_stmt stmt, byte[] paramName); @@ -1264,6 +1291,6 @@ public final class SQLite3Jni { static { // This MUST come after the SQLITE_MAX_... values or else // attempting to modify them silently fails. - init(new SQLite3Jni()); + init(SQLite3Jni.class); } } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index a709b96fea..df7b0ad82f 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -988,6 +988,8 @@ public class Tester1 { //listBoundMethods(); } final long timeEnd = System.nanoTime(); + affirm( SQLite3Jni.uncacheJniEnv() ); + affirm( !SQLite3Jni.uncacheJniEnv() ); outln("Tests done. Metrics:"); outln("\tAssertions checked: "+affirmCount); outln("\tDatabases opened: "+metrics.dbOpen); diff --git a/manifest b/manifest index c7fb5d16f3..295725bace 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C In\sthe\sJNI\sdocs,\snote\sthat\sthe\ssizeof\sSQLITE_TRANSIENT\sand\sSQLITE_STATIC\sshould\sideally\sbe\sthe\ssame\sas\sthe\splatform's\spointer\ssize. -D 2023-08-05T19:20:15.499 +C Add\sSQLite3Jni.uncacheJniEnv(),\sa\sway\sfor\sJava\sthreads\sto\sclear\stheir\sthread-specific\scached\sstate\sfrom\sthe\sJNI\sbindings\swhen\sthey're\sabout\sto\sterminate\s(or\sare\sotherwise\sdone\susing\sthe\slibrary). +D 2023-08-05T20:19:45.125 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 110e133920b469d1ff83ca4c1f23f8b8e6abce40c62b905bedfbdd6d117338a9 -F ext/jni/src/c/sqlite3-jni.h c3824071f3b7f09149ebc9921c5d0295fe343b31eb7a2c41fb6ff76cd2975f64 +F ext/jni/src/c/sqlite3-jni.c 17389f38639294b6ace3030e04a91f8b2e938e191f4c853075d4f55e94665a0c +F ext/jni/src/c/sqlite3-jni.h cd9b6367a260f55a14833861ceb1d87cb729c5414ba5d26fbb8854b0f22c7249 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -249,8 +249,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3b F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 0bdd63552ad5a8d8aa4429008c5da4607720f913ee995b622f8c8063ed2baf09 -F ext/jni/src/org/sqlite/jni/Tester1.java 732d26e858cfe32d664eab805ed8331fcef5cd460b19aa9afac8636f8a92bda3 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 213e7dfae620767deb421020cd705a131fd8ad3774e2bc9461eab46d12bf240c +F ext/jni/src/org/sqlite/jni/Tester1.java 868b5ea60b788a43f8b15c1b642015341fed8856abb1bb74e2eb4845ade50a4e F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2081,8 +2081,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7397973a2f191d75b149cf73a6d7ee1798820c1cd37d83af14e565067ede1b04 -R c054a9f4974b02bb5146402583a90c21 +P 7d4ac44ec419ed0474bdb9d237b97660cf0d8faba8fe686f6a914d7bc04dfa3b +R 4e66fd3de8c1a8416110ff65fc063c39 U stephan -Z 0e23b6ff523f68c333f5ec7c4d0652b3 +Z de9fd1b4a7e3cbc6e1092ca5c70548cf # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index af0a6667d3..5d1a062dd0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7d4ac44ec419ed0474bdb9d237b97660cf0d8faba8fe686f6a914d7bc04dfa3b \ No newline at end of file +7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f \ No newline at end of file From 28e95830ada662822066b9175fb734b355221806 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 21:35:58 +0000 Subject: [PATCH 057/148] Refactor the per-JNIEnv cache from a fixed-size static array to a linked list of dynamically-allocated entries. Uncache all per-db state (which is necessarily JNIEnv-specific) when the corresponding JNIEnv is uncached. FossilOrigin-Name: 9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33 --- ext/jni/src/c/sqlite3-jni.c | 401 ++++++++++----------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 15 +- manifest | 14 +- manifest.uuid | 2 +- 4 files changed, 221 insertions(+), 211 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 0c9862d402..56b86c2577 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -293,24 +293,13 @@ static const struct { enum { /** - Size of the per-JNIEnv cache. We have no way of knowing how many - distinct JNIEnv's will be used in any given run, but know that it - will normally be only 1. Perhaps (just speculating) different - threads use separate JNIEnvs? If that's the case, we don't(?) - have enough info to evict from the cache when those JNIEnvs - expire. - - If this ever fills up, we can refactor this to dynamically - allocate them. - */ - JNIEnvCache_SIZE = 10, - /** - Need enough space for (only) the library's NativePointerHolder - types, a fixed count known at build-time. If we add more than this - a fatal error will be triggered with a reminder to increase this. - This value needs to be exactly the number of entries in the - S3ClassNames object. The S3ClassNames entries are the keys for - this particular cache. + Size of the NativePointerHolder cache. Need enough space for + (only) the library's NativePointerHolder types, a fixed count + known at build-time. If we add more than this a fatal error will + be triggered with a reminder to increase this. This value needs + to be exactly the number of entries in the S3ClassNames + object. The S3ClassNames entries are the keys for this particular + cache. */ NphCache_SIZE = sizeof(S3ClassNames) / sizeof(char const *) }; @@ -370,12 +359,8 @@ struct JNIEnvCacheLine { jfieldID fidB; } jPhraseIter; #endif -#if 0 - /* TODO: refactor this cache as a linked list with malloc()'d entries, - rather than a fixed-size array in S3Global.envCache */ JNIEnvCacheLine * pPrev /* Previous entry in the linked list */; JNIEnvCacheLine * pNext /* Next entry in the linked list */; -#endif /** TODO: NphCacheLine *pNphHit; to help fast-track cache lookups, update this to point to the @@ -384,41 +369,12 @@ struct JNIEnvCacheLine { */ struct NphCacheLine nph[NphCache_SIZE]; }; -typedef struct JNIEnvCache JNIEnvCache; -struct JNIEnvCache { - struct JNIEnvCacheLine lines[JNIEnvCache_SIZE]; - unsigned int used; -}; static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ UNREF_G(p->klazz); memset(p, 0, sizeof(NphCacheLine)); } -static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ - JNIEnv * const env = p->env; - if(env){ - int i; - UNREF_G(p->globalClassObj); - UNREF_G(p->globalClassLong); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(p->jFtsExt); - UNREF_G(p->jPhraseIter.klazz); -#endif - for( i = 0; i < NphCache_SIZE; ++i ){ - NphCacheLine_clear(env, &p->nph[i]); - } - memset(p, 0, sizeof(JNIEnvCacheLine)); - } -} - -static void JNIEnvCache_clear(JNIEnvCache * const p){ - unsigned int i = 0; - for( ; i < p->used; ++i ){ - JNIEnvCacheLine_clear( &p->lines[i] ); - } -} - /** State for various hook callbacks. */ typedef struct JniHookState JniHookState; struct JniHookState{ @@ -476,7 +432,10 @@ static struct { JNIEnv when necessary. */ JavaVM * jvm; - struct JNIEnvCache envCache; + struct { + JNIEnvCacheLine * aHead /* Linked list of in-use instances */; + JNIEnvCacheLine * aFree /* Linked list of free instances */; + } envCache; struct { PerDbStateJni * aUsed /* Linked list of in-use instances */; PerDbStateJni * aFree /* Linked list of free instances */; @@ -512,11 +471,6 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){ return rv; } -static void s3jni_free(void * const p){ - if(p) sqlite3_free(p); -} - - /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own Java/JNI bindings. @@ -575,38 +529,183 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized - insofar as possible. Calls (*env)->FatalError() if the cache - fills up. That's hypothetically possible but "shouldn't happen." - If it does, we can dynamically allocate these instead. + insofar as possible. Calls (*env)->FatalError() if allocation of + an entry fails. That's hypothetically possible but "shouldn't happen." */ FIXME_THREADING -static struct JNIEnvCacheLine * S3Global_env_cache(JNIEnv * const env){ - struct JNIEnvCacheLine * row = 0; - int i = 0; - for( ; i < JNIEnvCache_SIZE; ++i ){ - row = &S3Global.envCache.lines[i]; - if(row->env == env){ +static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ + struct JNIEnvCacheLine * row = S3Global.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ ++S3Global.metrics.envCacheHits; return row; } - else if(!row->env) break; } ++S3Global.metrics.envCacheMisses; - if(i == JNIEnvCache_SIZE){ - (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full."); - return NULL; + row = S3Global.envCache.aFree; + if( row ){ + assert(!row->pPrev); + S3Global.envCache.aFree = row->pNext; + if( row->pNext ) row->pNext->pPrev = 0; + }else{ + row = sqlite3_malloc(sizeof(JNIEnvCacheLine)); + if( !row ){ + (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full.") + /* Does not return, but cc doesn't know that */; + return NULL; + } } - memset(row, 0, sizeof(JNIEnvCacheLine)); + memset(row, 0, sizeof(*row)); + row->pNext = S3Global.envCache.aHead; + if(row->pNext) row->pNext->pPrev = row; + S3Global.envCache.aHead = row; row->env = env; row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + EXCEPTION_IS_FATAL("Error getting reference to Object class."); row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + EXCEPTION_IS_FATAL("Error getting reference to Long class."); row->ctorLong1 = (*env)->GetMethodID(env, row->globalClassLong, "", "(J)V"); - ++S3Global.envCache.used; - //MARKER(("Added S3Global.envCache entry #%d.\n", S3Global.envCache.used)); + EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); return row; } +/** + Removes any Java references from s and clears its state. If + doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's + s is passed to s3jni_call_xDestroy() before any references are + cleared. It is legal to call this when the object has no Java + references. +*/ +static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){ + if(doXDestroy && s->klazz && s->jObj){ + s3jni_call_xDestroy(env, s->jObj, s->klazz); + } + UNREF_G(s->jObj); + UNREF_G(s->klazz); + memset(s, 0, sizeof(*s)); +} + +/** + Clears s's state and moves it to the free-list. +*/ +FIXME_THREADING +static void PerDbStateJni_set_aside(PerDbStateJni * const s){ + if(s){ + JNIEnv * const env = s->env; + assert(s->pDb && "Else this object is already in the free-list."); + //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); + assert(s->pPrev != s); + assert(s->pNext != s); + assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); + if(s->pNext) s->pNext->pPrev = s->pPrev; + if(s->pPrev) s->pPrev->pNext = s->pNext; + else if(S3Global.perDb.aUsed == s){ + assert(!s->pPrev); + S3Global.perDb.aUsed = s->pNext; + } +#define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY) + UNHOOK(trace, 0); + UNHOOK(progress, 0); + UNHOOK(commitHook, 0); + UNHOOK(rollbackHook, 0); + UNHOOK(updateHook, 0); + UNHOOK(collation, 1); + UNHOOK(collationNeeded, 1); + UNHOOK(busyHandler, 1); +#undef UNHOOK + UNREF_G(s->jDb); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(s->jFtsApi); +#endif + memset(s, 0, sizeof(PerDbStateJni)); + s->pNext = S3Global.perDb.aFree; + if(s->pNext) s->pNext->pPrev = s; + S3Global.perDb.aFree = s; + //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); + //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); + } +} + +/** + Requires that p has been snipped from any linked list it is + in. Clears all Java refs p holds and zeroes out p. +*/ +static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ + JNIEnv * const env = p->env; + if(env){ + int i; + UNREF_G(p->globalClassObj); + UNREF_G(p->globalClassLong); +#ifdef SQLITE_ENABLE_FTS5 + UNREF_G(p->jFtsExt); + UNREF_G(p->jPhraseIter.klazz); +#endif + for( i = 0; i < NphCache_SIZE; ++i ){ + NphCacheLine_clear(env, &p->nph[i]); + } + memset(p, 0, sizeof(JNIEnvCacheLine)); + } +} + +/** + Cleans up all state in S3Global.perDb for th given JNIEnv. + Results are undefined if a Java-side db uses the API + from the given JNIEnv after this call. +*/ +FIXME_THREADING +static void PerDbStateJni_free_for_env(JNIEnv *env){ + PerDbStateJni * ps = S3Global.perDb.aUsed; + PerDbStateJni * pNext = 0; + for( ; ps; ps = pNext ){ + pNext = ps->pNext; + if(ps->env == env){ + PerDbStateJni * const pPrev = ps->pPrev; + PerDbStateJni_set_aside(ps); + assert(pPrev ? pPrev->pNext!=ps : 1); + pNext = pPrev; + } + } +} + +/** + Uncache any state for the given JNIEnv, clearing all Java + references the cache owns. Returns true if env was cached and false + if it was not found in the cache. + + Also passes env to PerDbStateJni_free_for_env() to free up + what would otherwise be stale references. +*/ +static int S3Global_JNIEnvCache_uncache(JNIEnv * const env){ + struct JNIEnvCacheLine * row = S3Global.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ + break; + } + } + if( !row ) return 0; + if( row->pNext ) row->pNext->pPrev = row->pPrev; + if( row->pPrev ) row->pPrev->pNext = row->pNext; + if( S3Global.envCache.aHead == row ){ + assert( !row->pPrev ); + S3Global.envCache.aHead = row->pNext; + } + JNIEnvCacheLine_clear(row); + assert( !row->pNext ); + assert( !row->pPrev ); + row->pNext = S3Global.envCache.aFree; + if( row->pNext ) row->pNext->pPrev = row; + S3Global.envCache.aFree = row; + PerDbStateJni_free_for_env(env); + return 1; +} + +static void S3Global_JNIEnvCache_clear(void){ + while( S3Global.envCache.aHead ){ + S3Global_JNIEnvCache_uncache( S3Global.envCache.aHead->env ); + } +} + /** Searches the NativePointerHolder cache for the given combination. If it finds one, it returns it as-is. If it doesn't AND the cache @@ -640,7 +739,7 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char * looking up class objects can be expensive, so they should be cached as well. */ - struct JNIEnvCacheLine * const envRow = S3Global_env_cache(env); + struct JNIEnvCacheLine * const envRow = S3Global_JNIEnvCache_cache(env); struct NphCacheLine * freeSlot = 0; struct NphCacheLine * cacheLine = 0; int i; @@ -787,63 +886,6 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, job return rv; } -/** - Removes any Java references from s and clears its state. If - doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's - s is passed to s3jni_call_xDestroy() before any references are - cleared. It is legal to call this when the object has no Java - references. -*/ -static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){ - if(doXDestroy && s->klazz && s->jObj){ - s3jni_call_xDestroy(env, s->jObj, s->klazz); - } - UNREF_G(s->jObj); - UNREF_G(s->klazz); - memset(s, 0, sizeof(*s)); -} - -/** - Clears s's state and moves it to the free-list. -*/ -FIXME_THREADING -static void PerDbStateJni_set_aside(PerDbStateJni * const s){ - if(s){ - JNIEnv * const env = s->env; - assert(s->pDb && "Else this object is already in the free-list."); - //MARKER(("state@%p for db@%p setting aside\n", s, s->pDb)); - assert(s->pPrev != s); - assert(s->pNext != s); - assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); - if(s->pNext) s->pNext->pPrev = s->pPrev; - if(s->pPrev) s->pPrev->pNext = s->pNext; - else if(S3Global.perDb.aUsed == s){ - assert(!s->pPrev); - S3Global.perDb.aUsed = s->pNext; - } -#define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY) - UNHOOK(trace, 0); - UNHOOK(progress, 0); - UNHOOK(commitHook, 0); - UNHOOK(rollbackHook, 0); - UNHOOK(updateHook, 0); - UNHOOK(collation, 1); - UNHOOK(collationNeeded, 1); - UNHOOK(busyHandler, 1); -#undef UNHOOK - UNREF_G(s->jDb); -#ifdef SQLITE_ENABLE_FTS5 - UNREF_G(s->jFtsApi); -#endif - memset(s, 0, sizeof(PerDbStateJni)); - s->pNext = S3Global.perDb.aFree; - if(s->pNext) s->pNext->pPrev = s; - S3Global.perDb.aFree = s; - //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); - //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); - } -} - static void PerDbStateJni_dump(PerDbStateJni *s){ MARKER(("PerDbStateJni->env @ %p\n", s->env)); MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb)); @@ -894,28 +936,6 @@ static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, return s; } -/** - Cleans up and frees all state in S3Global.perDb. -*/ -FIXME_THREADING -static void PerDbStateJni_free_all(void){ - PerDbStateJni * ps = S3Global.perDb.aUsed; - PerDbStateJni * pSNext = 0; - for( ; ps; ps = pSNext ){ - pSNext = ps->pNext; - PerDbStateJni_set_aside(ps); - assert(pSNext ? !pSNext->pPrev : 1); - } - assert( 0==S3Global.perDb.aUsed ); - ps = S3Global.perDb.aFree; - S3Global.perDb.aFree = 0; - pSNext = 0; - for( ; ps; ps = pSNext ){ - pSNext = ps->pNext; - s3jni_free(pSNext); - } -} - /** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. @@ -1322,7 +1342,7 @@ static int udf_args(JNIEnv *env, *jArgv = 0; if(!jcx) goto error_oom; ja = (*env)->NewObjectArray(env, argc, - S3Global_env_cache(env)->globalClassObj, + S3Global_JNIEnvCache_cache(env)->globalClassObj, NULL); if(!ja) goto error_oom; for(i = 0; i < argc; ++i){ @@ -2015,7 +2035,7 @@ JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_finalize(pStmt); setNativePointer(env, jpStmt, 0, S3ClassNames.sqlite3_stmt); @@ -2086,7 +2106,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass sqlite3_stmt * pStmt = 0; const char * zTail = 0; jbyte * const pBuf = JBA_TOC(baSql); - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); jobject const pOldStmt = stmt_set_current(jc, jOutStmt); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); @@ -2181,7 +2201,7 @@ JDECL(jint,1reset)(JENV_JSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_reset(pStmt); (void)stmt_set_current(jc, pPrev); @@ -2374,11 +2394,9 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ } JDECL(jint,1shutdown)(JENV_JSELF){ - PerDbStateJni_free_all(); - JNIEnvCache_clear(&S3Global.envCache); - /* Do not clear S3Global.jvm or the global refs to specific classes: - it's legal to call sqlite3_initialize() again to restart the - lib. */ + S3Global_JNIEnvCache_clear(); + /* Do not clear S3Global.jvm: it's legal to call + sqlite3_initialize() again to restart the lib. */ return sqlite3_shutdown(); } @@ -2386,7 +2404,7 @@ JDECL(jint,1step)(JENV_JSELF,jobject jStmt){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); if(pStmt){ - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); jobject const jPrevStmt = stmt_set_current(jc, jStmt); rc = sqlite3_step(pStmt); (void)stmt_set_current(jc, jPrevStmt); @@ -2400,7 +2418,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ jobject jX = NULL /* the tracer's X arg */; jobject jP = NULL /* the tracer's P arg */; jobject jPUnref = NULL /* potentially a local ref to jP */; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); int rc; switch(traceflag){ case SQLITE_TRACE_STMT: @@ -2660,7 +2678,6 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ SO(JniHookState); SO(PerDbStateJni); SO(S3Global); - SO(JNIEnvCache); SO(S3ClassNames); printf("\t(^^^ %u NativePointerHolder subclasses)\n", (unsigned)(sizeof(S3ClassNames) / sizeof(const char *))); @@ -2782,7 +2799,7 @@ static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ instance, or NULL on OOM. */ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ - JNIEnvCacheLine * const row = S3Global_env_cache(env); + JNIEnvCacheLine * const row = S3Global_JNIEnvCache_cache(env); if( !row->jFtsExt ){ row->jFtsExt = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, s3jni_ftsext()); @@ -3056,7 +3073,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); Fts5PhraseIter iter; int rc, iCol = 0, iOff = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3073,7 +3090,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); Fts5PhraseIter iter; int rc, iCol = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3089,7 +3106,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); Fts5PhraseIter iter; int iCol = 0, iOff = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3104,7 +3121,7 @@ JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, JDECLFtsXA(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); Fts5PhraseIter iter; int iCol = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3158,7 +3175,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, JDECLFtsXA(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; if( !klazz ){ @@ -3247,7 +3264,7 @@ static jint s3jni_fts5_xTokenize(JENV_JSELF, const char *zClassName, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_env_cache(env); + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); struct s3jni_xQueryPhraseState s; int rc = 0; jbyte * const pText = JBA_TOC(jbaText); @@ -3327,32 +3344,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ */ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JNIEnv * const env, jclass self){ - struct JNIEnvCacheLine * row = 0; - int i; - for( i = 0; i < JNIEnvCache_SIZE; ++i ){ - row = &S3Global.envCache.lines[i]; - if(row->env == env){ - break; - } - } - if( i==JNIEnvCache_SIZE ){ - //MARKER(("The given JNIEnv is not currently cached.\n")); - return JNI_FALSE; - } - //MARKER(("Uncaching S3Global.envCache entry #%d.\n", i)); - assert(S3Global.envCache.used >= i); - JNIEnvCacheLine_clear(row); - /** - Move all entries down one slot. memmove() would be faster. We'll - eventually turn this cache into a dynamically-allocated linked - list, anyway, so this part will go away. - */ - for( ++i ; i < JNIEnvCache_SIZE; ++i ){ - S3Global.envCache.lines[i-i] = S3Global.envCache.lines[i]; - } - memset(&S3Global.envCache.lines[i], 0, sizeof(JNIEnvCacheLine)); - --S3Global.envCache.used; - return JNI_TRUE; + return S3Global_JNIEnvCache_uncache(env) ? JNI_TRUE : JNI_FALSE; } @@ -3413,13 +3405,18 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klaz //jclass const klazz = (*env)->GetObjectClass(env, sJni); const ConfigFlagEntry * pConfFlag; memset(&S3Global, 0, sizeof(S3Global)); - (void)S3Global_env_cache(env); - assert( 1 == S3Global.envCache.used ); - assert( env == S3Global.envCache.lines[0].env ); - assert( 0 != S3Global.envCache.lines[0].globalClassObj ); if( (*env)->GetJavaVM(env, &S3Global.jvm) ){ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); + return; } + (void)S3Global_JNIEnvCache_cache(env); + if( !S3Global.envCache.aHead ){ + (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); + return; + } + assert( 1 == S3Global.metrics.envCacheMisses ); + assert( env == S3Global.envCache.aHead->env ); + assert( 0 != S3Global.envCache.aHead->globalClassObj ); for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 0320b102fe..9c8e1321c4 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -79,9 +79,22 @@ public final class SQLite3Jni { library again after that "should" re-initialize the cache on demand, but that's untested. + This call forcibly wipes out all cached information for the + current JNIEnv, a side-effect of which is that behavior is + undefined if any database objects are (A) still active at the + time it is called _and_ (B) calls are subsequently made into the + library with such a database. Doing so will, at best, lead to a + crash. It worst, it will lead to the db possibly misbehaving + because some of its Java-bound state has been cleared. There is + no immediate harm in (A) so long as condition (B) is not met. + This process does _not_ actually close any databases or finalize + any prepared statements. For proper library behavior, and to + avoid C-side leaks, be sure to close them before calling this + function. + Calling this from the main application thread is not strictly required but is "polite." Additional threads must call this - before ending or they will leak cache entries in the C memory, + before ending or they will leak cache entries in the C heap, which in turn may keep numerous Java-side global references active. diff --git a/manifest b/manifest index 295725bace..9898624e78 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sSQLite3Jni.uncacheJniEnv(),\sa\sway\sfor\sJava\sthreads\sto\sclear\stheir\sthread-specific\scached\sstate\sfrom\sthe\sJNI\sbindings\swhen\sthey're\sabout\sto\sterminate\s(or\sare\sotherwise\sdone\susing\sthe\slibrary). -D 2023-08-05T20:19:45.125 +C Refactor\sthe\sper-JNIEnv\scache\sfrom\sa\sfixed-size\sstatic\sarray\sto\sa\slinked\slist\sof\sdynamically-allocated\sentries.\sUncache\sall\sper-db\sstate\s(which\sis\snecessarily\sJNIEnv-specific)\swhen\sthe\scorresponding\sJNIEnv\sis\suncached. +D 2023-08-05T21:35:58.833 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 17389f38639294b6ace3030e04a91f8b2e938e191f4c853075d4f55e94665a0c +F ext/jni/src/c/sqlite3-jni.c a894cb1b6a7479d376d98f7df0e29a713d2b6b6cbc0d4543505b90eb38593592 F ext/jni/src/c/sqlite3-jni.h cd9b6367a260f55a14833861ceb1d87cb729c5414ba5d26fbb8854b0f22c7249 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -249,7 +249,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3b F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 213e7dfae620767deb421020cd705a131fd8ad3774e2bc9461eab46d12bf240c +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f8fec24eea4ff9a62467648f377391506883eff4bedfc50d69d8cb3dc78d1ab9 F ext/jni/src/org/sqlite/jni/Tester1.java 868b5ea60b788a43f8b15c1b642015341fed8856abb1bb74e2eb4845ade50a4e F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2081,8 +2081,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7d4ac44ec419ed0474bdb9d237b97660cf0d8faba8fe686f6a914d7bc04dfa3b -R 4e66fd3de8c1a8416110ff65fc063c39 +P 7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f +R c28f39c95821b6209d9e0ded55b1b7ca U stephan -Z de9fd1b4a7e3cbc6e1092ca5c70548cf +Z 6d5410b49d781e986fb3ebb97da80215 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5d1a062dd0..27cf161b3c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f \ No newline at end of file +9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33 \ No newline at end of file From 153288dc89cf150853061e840f5b34381eec65fb Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 5 Aug 2023 22:41:36 +0000 Subject: [PATCH 058/148] Bind sqlite3_set_authorizer() to JNI. FossilOrigin-Name: e0fa03135942cd2fe732a74510d380ba78ab230c452168e638f32b4aee04b3f7 --- ext/jni/src/c/sqlite3-jni.c | 67 ++++++++++++++++++++++ ext/jni/src/c/sqlite3-jni.h | 8 +++ ext/jni/src/org/sqlite/jni/Authorizer.java | 36 ++++++++++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 11 +++- ext/jni/src/org/sqlite/jni/Tester1.java | 27 ++++++++- manifest | 19 +++--- manifest.uuid | 2 +- 7 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/Authorizer.java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 56b86c2577..aba4db5a91 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -412,6 +412,7 @@ struct PerDbStateJni { JniHookState rollbackHook; JniHookState trace; JniHookState updateHook; + JniHookState authHook; #ifdef SQLITE_ENABLE_FTS5 jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; #endif @@ -610,6 +611,7 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ UNHOOK(commitHook, 0); UNHOOK(rollbackHook, 0); UNHOOK(updateHook, 0); + UNHOOK(authHook, 0); UNHOOK(collation, 1); UNHOOK(collationNeeded, 1); UNHOOK(busyHandler, 1); @@ -2388,6 +2390,71 @@ JDECL(jobject,1rollback_1hook)(JENV_JSELF,jobject jDb, jobject jHook){ return s3jni_commit_rollback_hook(0, env, jDb, jHook); } +/* sqlite3_set_authorizer() callback proxy. */ +static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, + const char*z2,const char*z3){ + PerDbStateJni * const ps = pState; + JNIEnv * const env = ps->env; + jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; + jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; + jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; + jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; + JniHookState const * const pHook = &ps->authHook; + int rc; + + assert( pHook->jObj ); + rc = (*env)->CallIntMethod(env, pHook->jObj, pHook->midCallback, (jint)op, + s0, s1, s3, s3); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("sqlite3_set_authorizer() callback"); + EXCEPTION_CLEAR; + } + UNREF_L(s0); + UNREF_L(s1); + UNREF_L(s2); + UNREF_L(s3); + return rc; +} + +JDECL(jint,1set_1authorizer)(JENV_JSELF,jobject jDb, jobject jHook){ + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + JniHookState * const pHook = ps ? &ps->authHook : 0; + + if( !ps ) return SQLITE_MISUSE; + else if( !jHook ){ + JniHookState_unref(env, pHook, 0); + return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); + }else{ + int rc = 0; + if( pHook->jObj ){ + if( (*env)->IsSameObject(env, pHook->jObj, jHook) ){ + /* Same object - this is a no-op. */ + return 0; + } + JniHookState_unref(env, pHook, 0); + } + pHook->jObj = REF_G(jHook); + pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); + pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, + "xAuth", + "(I" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Ljava/lang/String;" + ")I"); + IFTHREW { + JniHookState_unref(env, pHook, 0); + return s3jni_db_error(ps->pDb, SQLITE_ERROR, + "Error setting up Java parts of authorizer hook."); + } + rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); + if( rc ) JniHookState_unref(env, pHook, 0); + return rc; + } +} + + JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), (sqlite3_int64)rowId); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index d05bf77e45..45a046c99d 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1371,6 +1371,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 JNIEXPORT jobject JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1rollback_1hook (JNIEnv *, jclass, jobject, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_set_authorizer + * Signature: (Lorg/sqlite/jni/sqlite3;Lorg/sqlite/jni/Authorizer;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1set_1authorizer + (JNIEnv *, jclass, jobject, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_set_last_insert_rowid diff --git a/ext/jni/src/org/sqlite/jni/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java new file mode 100644 index 0000000000..b10567716c --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/Authorizer.java @@ -0,0 +1,36 @@ +/* +** 2023-08-05 +** +** 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 use with sqlite3_set_authorizer(). +*/ +public interface Authorizer { + /** + Must functions as described for the sqlite3_set_authorizer() + callback, with one caveat: the string values passed here were + initially (at the C level) encoded in standard UTF-8. If they + contained any constructs which are not compatible with MUTF-8, + these strings will not have the expected values. The strings + passed through the authorizer would only be adversely affected by + that if the database tables and columns use "highly exotic" + names. Any names which contain no NUL bytes, nor characters + outside of the Basic Multilingual Plane are unaffected by this + discrepancy. + + Must not throw. + */ + int xAuth(int opId, @Nullable String s1, @Nullable String s2, + @Nullable String s3, @Nullable String s4); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 9c8e1321c4..1f7180f14a 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -103,12 +103,13 @@ public final class SQLite3Jni { primarily for testing of the JNI bindings and is not information which client-level code should use to make any informed decisions. -*/ + */ public static synchronized native boolean uncacheJniEnv(); ////////////////////////////////////////////////////////////////////// - // Maintenance reminder: please keep the functions alphabetized. - // The SQLITE_... values. on the other hand, are grouped by category. + // Maintenance reminder: please keep the sqlite3_.... functions + // alphabetized. The SQLITE_... values. on the other hand, are + // grouped by category. public static int sqlite3_bind_blob(@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data){ @@ -725,6 +726,10 @@ public final class SQLite3Jni { public static native RollbackHook sqlite3_rollback_hook(@NotNull sqlite3 db, @Nullable RollbackHook hook); + //! Sets or unsets (if auth is null) the current authorizer. + public static native int sqlite3_set_authorizer(@NotNull sqlite3 db, + @Nullable Authorizer auth); + public static native void sqlite3_set_last_insert_rowid(@NotNull sqlite3 db, long rowid); public static native int sqlite3_sleep(int ms); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index df7b0ad82f..a5020663a6 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -82,7 +82,8 @@ public class Tester1 { } if( 0==sqlChunk.length ) break; rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); - affirm(0 == rc); + if(throwOnError) affirm(0 == rc); + else if( 0!=rc ) break; pos = oTail.getValue(); affirm(0 != stmt.getNativePointer()); while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ @@ -97,6 +98,7 @@ public class Tester1 { } } } + sqlite3_finalize(stmt); if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; return rc; } @@ -949,6 +951,28 @@ public class Tester1 { } } + private static void testAuthorizer(){ + final sqlite3 db = createNewDb(); + final ValueHolder counter = new ValueHolder<>(0); + final ValueHolder authRc = new ValueHolder<>(0); + final Authorizer auth = new Authorizer(){ + public int xAuth(int op, String s0, String s1, String s2, String s3){ + ++counter.value; + //outln("xAuth(): "+s0+" "+s1+" "+s2+" "+s3); + return authRc.value; + } + }; + execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); + sqlite3_set_authorizer(db, auth); + execSql(db, "UPDATE t SET a=1"); + affirm( 1 == counter.value ); + authRc.value = SQLITE_DENY; + int rc = execSql(db, false, "UPDATE t SET a=2"); + affirm( SQLITE_AUTH==rc ); + // TODO: expand these tests considerably + sqlite3_close(db); + } + private static void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); @@ -981,6 +1005,7 @@ public class Tester1 { testCommitHook(); testRollbackHook(); testUpdateHook(); + testAuthorizer(); testFts5(); //testSleep(); if(liArgs.indexOf("-v")>0){ diff --git a/manifest b/manifest index 9898624e78..84a488eff3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Refactor\sthe\sper-JNIEnv\scache\sfrom\sa\sfixed-size\sstatic\sarray\sto\sa\slinked\slist\sof\sdynamically-allocated\sentries.\sUncache\sall\sper-db\sstate\s(which\sis\snecessarily\sJNIEnv-specific)\swhen\sthe\scorresponding\sJNIEnv\sis\suncached. -D 2023-08-05T21:35:58.833 +C Bind\ssqlite3_set_authorizer()\sto\sJNI. +D 2023-08-05T22:41:36.472 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,9 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c a894cb1b6a7479d376d98f7df0e29a713d2b6b6cbc0d4543505b90eb38593592 -F ext/jni/src/c/sqlite3-jni.h cd9b6367a260f55a14833861ceb1d87cb729c5414ba5d26fbb8854b0f22c7249 +F ext/jni/src/c/sqlite3-jni.c b81dcf92a1fbd7ada98dbc0f526b02e9e97e2be84cb331092e933c7d8feeafe2 +F ext/jni/src/c/sqlite3-jni.h 1db6075134d7efc71f634e4a212e41d0178346e68c7c615f220d753e4bd23382 +F ext/jni/src/org/sqlite/jni/Authorizer.java 189e7fa2155466ded5a52eed1ccb46581b5b45d70fcef49f538c0b93ea88e337 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -249,8 +250,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3b F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java f8fec24eea4ff9a62467648f377391506883eff4bedfc50d69d8cb3dc78d1ab9 -F ext/jni/src/org/sqlite/jni/Tester1.java 868b5ea60b788a43f8b15c1b642015341fed8856abb1bb74e2eb4845ade50a4e +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 850012ad011d5d60a8aaaf9b8db562c60277988982793135814892e82ac77b97 +F ext/jni/src/org/sqlite/jni/Tester1.java 214985f8a700ac56a174809c04196215fef5d7bb8b86e095d9d0abf436349ffb F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2081,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7468f8761bece58f7ced3d112bbe2fb454432d9c54c9b96cedb5a15bc2926d0f -R c28f39c95821b6209d9e0ded55b1b7ca +P 9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33 +R bf3d1c30171f035a2002cb68468caf2a U stephan -Z 6d5410b49d781e986fb3ebb97da80215 +Z a910bd2897a92249409319f7eba80e4a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 27cf161b3c..1516c35375 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33 \ No newline at end of file +e0fa03135942cd2fe732a74510d380ba78ab230c452168e638f32b4aee04b3f7 \ No newline at end of file From 1bce6b468edd0e0763a24a9f126aa9c5461a0689 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 10:14:53 +0000 Subject: [PATCH 059/148] Bind sqlite3_db_filename() and (closely related) (A) add many more docs about the UTF-8/MUTF-8 discrepancy (B) start adding internals to enable us to perform the standard-UTF-8-to-Java conversion from C. FossilOrigin-Name: 586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff --- ext/jni/src/c/sqlite3-jni.c | 76 ++++++++++++++++++-- ext/jni/src/c/sqlite3-jni.h | 8 +++ ext/jni/src/org/sqlite/jni/Authorizer.java | 8 +-- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 81 +++++++++++++++++++++- ext/jni/src/org/sqlite/jni/Tester1.java | 1 + manifest | 20 +++--- manifest.uuid | 2 +- 7 files changed, 171 insertions(+), 25 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index aba4db5a91..646ecab06c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -187,7 +187,10 @@ #define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_value) #define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_context) /* Helpers for Java value reference management. */ -#define REF_G(VAR) (*env)->NewGlobalRef(env, VAR) +static inline jobject new_global_ref(JNIEnv *env, jobject v){ + return v ? (*env)->NewGlobalRef(env, v) : NULL; +} +#define REF_G(VAR) new_global_ref(env, (VAR)) #define REF_L(VAR) (*env)->NewLocalRef(env, VAR) #define UNREF_G(VAR) if(VAR) (*env)->DeleteGlobalRef(env, (VAR)) #define UNREF_L(VAR) if(VAR) (*env)->DeleteLocalRef(env, (VAR)) @@ -337,9 +340,15 @@ struct NphCacheLine { typedef struct JNIEnvCacheLine JNIEnvCacheLine; struct JNIEnvCacheLine { JNIEnv *env /* env in which this cache entry was created */; + //! The various refs to global classes might be cacheable a single + // time globally. Information online seems inconsistent on that + // point. jclass globalClassObj /* global ref to java.lang.Object */; jclass globalClassLong /* global ref to java.lang.Long */; + jclass globalClassString /* global ref to java.lang.String */; + jobject globalClassCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; jobject currentStmt /* Current Java sqlite3_stmt object being prepared, stepped, reset, or finalized. Needed for tracing, the @@ -563,11 +572,32 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ row->env = env; row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object")); EXCEPTION_IS_FATAL("Error getting reference to Object class."); + row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long")); EXCEPTION_IS_FATAL("Error getting reference to Long class."); row->ctorLong1 = (*env)->GetMethodID(env, row->globalClassLong, "", "(J)V"); EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); + + row->globalClassString = REF_G((*env)->FindClass(env,"java/lang/String")); + EXCEPTION_IS_FATAL("Error getting reference to String class."); + row->ctorStringBA = + (*env)->GetMethodID(env, row->globalClassString, + "", "([BLjava/nio/charset/Charset;)V"); + EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); + + { /* StandardCharsets.UTF_8 */ + jfieldID fUtf8; + jclass const klazzSC = + (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); + EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); + fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", + "Ljava/nio/charset/Charset;"); + EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); + row->globalClassCharsetUtf8 = + REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); + EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); + } return row; } @@ -639,6 +669,9 @@ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ int i; UNREF_G(p->globalClassObj); UNREF_G(p->globalClassLong); + UNREF_G(p->globalClassString); + UNREF_G(p->globalClassCharsetUtf8); + UNREF_G(p->currentStmt); #ifdef SQLITE_ENABLE_FTS5 UNREF_G(p->jFtsExt); UNREF_G(p->jPhraseIter.klazz); @@ -1993,12 +2026,43 @@ JDECL(jint,1create_1function)(JENV_JSELF, jobject jDb, jstring jFuncName, return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); } -/* -JDECL(jint,1create_1window_1function)(JENV_JSELF, jstring jFuncName, jint nArg, - jint eTextRep, jobject jFunctor){ - return create_function_mega(env, jFuncName, nArg, eTextRep, jFunctor); + +JDECL(jbyteArray,1db_1filename)(JENV_JSELF, jobject jDb, jbyteArray jDbName){ +#if 1 + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + jbyte *zFilename = (ps && jDbName) ? JBA_TOC(jDbName) : 0; + const char *zRv; + jbyteArray jRv = 0; + + if( !ps || (jDbName && !zFilename) ) return 0; + zRv = sqlite3_db_filename(ps->pDb, (const char *)zFilename); + if( zRv ){ + const int n = sqlite3Strlen30(zRv); + jRv = (*env)->NewByteArray(env, (jint)n); + if( jRv ){ + (*env)->SetByteArrayRegion(env, jRv, 0, (jint)n, (const jbyte *)zRv); + } + } + JBA_RELEASE(jDbName, zFilename); + return jRv; +#else + /* For comparison, this impl expects a jstring jDbName and returns a + jstring for significant code savings but it's not + MUTF-8-safe. With this impl, the Java-side byte-array-using + sqlite3_db_filename() impl is unnecessary. */ + JDECL(jstring,1db_1filename)(JENV_JSELF, jobject jDb, jstring jDbName){ + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + const char *zFilename = (ps && jDbName) ? JSTR_TOC(jDbName) : 0; + const char *zRv; + + if( !ps || (jDbName && !zFilename)) return 0; + zRv = sqlite3_db_filename(ps->pDb, zFilename ? zFilename : "main"); + JSTR_RELEASE(jDbName, zFilename); + return zRv ? (*env)->NewStringUTF(env, zRv) : 0; +} +#endif + } -*/ JDECL(jstring,1errmsg)(JENV_JSELF, jobject jpDb){ return (*env)->NewStringUTF(env, sqlite3_errmsg(PtrGet_sqlite3(jpDb))); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 45a046c99d..ba24cb391a 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1091,6 +1091,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1create_1function JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count (JNIEnv *, jclass, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_filename + * Signature: (Lorg/sqlite/jni/sqlite3;[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename + (JNIEnv *, jclass, jobject, jbyteArray); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_errcode diff --git a/ext/jni/src/org/sqlite/jni/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java index b10567716c..3fd6861fec 100644 --- a/ext/jni/src/org/sqlite/jni/Authorizer.java +++ b/ext/jni/src/org/sqlite/jni/Authorizer.java @@ -22,12 +22,8 @@ public interface Authorizer { callback, with one caveat: the string values passed here were initially (at the C level) encoded in standard UTF-8. If they contained any constructs which are not compatible with MUTF-8, - these strings will not have the expected values. The strings - passed through the authorizer would only be adversely affected by - that if the database tables and columns use "highly exotic" - names. Any names which contain no NUL bytes, nor characters - outside of the Basic Multilingual Plane are unaffected by this - discrepancy. + these strings will not have the expected values. For further + details, see the documentation for the SQLite3Jni class. Must not throw. */ diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 1f7180f14a..ebf60c10f6 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -60,7 +60,67 @@ import java.lang.annotation.ElementType; https://sqlite.org/c3ref/intro.html - A small handful of Java-specific APIs have been added. + A handful of Java-specific APIs have been added. + + + ****************************************************************** + *** Warning regarding Java's Modified UTF-8 vs standard UTF-8: *** + ****************************************************************** + + SQLite internally uses UTF-8 encoding, whereas Java natively uses + UTF-16. Java JNI has routines for converting to and from UTF-8, + _but_ JNI uses what its docs call modified UTF-8 (see links below) + Care must be taken when converting Java strings to or from standard + UTF-8 to ensure that the proper conversion is performed. In short, + Java's `String.getBytes(StandardCharsets.UTF_8)` performs the proper + conversion in Java, and there are no JNI C APIs for that conversion + (JNI's `NewStringUTF()` requires its input to be in MUTF-8). + + The known consequences and limitations this discrepancy places on + the SQLite3 JNI binding include: + + - Any functions which return client-side data from a database + take extra care to perform proper conversion, at the cost of + efficiency. + + - Functions which return database identifiers require those + identifiers to have identical representations in UTF-8 and + MUTF-8. They do not perform such conversions (A) because of the + much lower risk of an encoding discrepancy and (B) to avoid + significant extra code involved (see both the Java- and C-side + implementations of sqlite3_db_filename() for an example). Names + of databases, tables, columns, collations, and functions MUST NOT + contain characters which differ in MUTF-8 and UTF-8, or certain + APIs will mis-translate them on their way between languages + (possibly leading to a crash). + + - sqlite3_trace_v2() is also currently affected by this, in that + it requires that traced SQL statements be compatible with + MUTF-8. The alternative would be to perform two extra layers of + conversion for that performance-sensitive function: one from + UTF-8 to a byte-array before passing the data from C to Java, + and then from byte-array to String in the tracer implementation. + + - C functions which take C-style strings without a length argument + require special care when taking input from Java. In particular, + Java strings converted to byte arrays for encoding purposes are + not NUL-terminated, and conversion to a Java byte array must be + careful to add one. Functions which take a length do not require + this. Search the SQLite3Jni class for "\0" for many examples. + + - Similarly, C-side code which deals with strings which might not be + NUL-terminated (e.g. while tokenizing in FTS5-related code) cannot + use JNI's new-string functions to return them to Java because none + of those APIs take a string-length argument. Such cases must + return byte arrays instead of strings. + + Further reading: + + - https://stackoverflow.com/questions/57419723 + - https://stackoverflow.com/questions/7921016 + - https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html#unicode + - https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 + */ public final class SQLite3Jni { static { @@ -84,7 +144,7 @@ public final class SQLite3Jni { undefined if any database objects are (A) still active at the time it is called _and_ (B) calls are subsequently made into the library with such a database. Doing so will, at best, lead to a - crash. It worst, it will lead to the db possibly misbehaving + crash. Azt worst, it will lead to the db possibly misbehaving because some of its Java-bound state has been cleared. There is no immediate harm in (A) so long as condition (B) is not met. This process does _not_ actually close any databases or finalize @@ -344,6 +404,23 @@ public final class SQLite3Jni { public static native int sqlite3_data_count(@NotNull sqlite3_stmt stmt); + /** In order to support the full range of UTF-8 filenames, we + require an extra layer of conversion via a byte[]. */ + private static native byte[] sqlite3_db_filename(@NotNull sqlite3 db, + @NotNull byte dbName[]); + + /** + As for the C API of the same name except that if dbName is null then + "main" is assumed. + */ + public static String sqlite3_db_filename(@NotNull sqlite3 db, + @Nullable String dbName){ + final byte[] bName = + (((null == dbName) ? "main" : dbName)+"\0").getBytes(StandardCharsets.UTF_8); + final byte[] rv = sqlite3_db_filename(db, bName); + return (null == rv) ? null : new String(rv, StandardCharsets.UTF_8); + } + public static native int sqlite3_errcode(@NotNull sqlite3 db); public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index a5020663a6..334484166c 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -734,6 +734,7 @@ public class Tester1 { rc = sqlite3_open(dbName, db2); ++metrics.dbOpen; affirm( 0 == rc ); + affirm( sqlite3_db_filename(db1, null).endsWith(dbName) ); final ValueHolder xDestroyed = new ValueHolder<>(false); final ValueHolder xBusyCalled = new ValueHolder<>(0); diff --git a/manifest b/manifest index 84a488eff3..bc94970f53 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_set_authorizer()\sto\sJNI. -D 2023-08-05T22:41:36.472 +C Bind\ssqlite3_db_filename()\sand\s(closely\srelated)\s(A)\sadd\smany\smore\sdocs\sabout\sthe\sUTF-8/MUTF-8\sdiscrepancy\s(B)\sstart\sadding\sinternals\sto\senable\sus\sto\sperform\sthe\sstandard-UTF-8-to-Java\sconversion\sfrom\sC. +D 2023-08-06T10:14:53.465 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,9 +232,9 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c b81dcf92a1fbd7ada98dbc0f526b02e9e97e2be84cb331092e933c7d8feeafe2 -F ext/jni/src/c/sqlite3-jni.h 1db6075134d7efc71f634e4a212e41d0178346e68c7c615f220d753e4bd23382 -F ext/jni/src/org/sqlite/jni/Authorizer.java 189e7fa2155466ded5a52eed1ccb46581b5b45d70fcef49f538c0b93ea88e337 +F ext/jni/src/c/sqlite3-jni.c 88c18f2f1dd8064ca3d264bcda0df950b57bc0f5b9d8bfeb43bdd3f5be723ab8 +F ext/jni/src/c/sqlite3-jni.h 03c61c4f84c028169633392d7eb06caa6000e8bf3c0a3f7ac44e645dedbbfb9a +F ext/jni/src/org/sqlite/jni/Authorizer.java 8dde03bbe50896d2f426240a4af4dcb6d98b655af84fe6ed86e637f5d5ac1fc8 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -250,8 +250,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 013f2b5fe569d0585a695f5cfa605a3b F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 850012ad011d5d60a8aaaf9b8db562c60277988982793135814892e82ac77b97 -F ext/jni/src/org/sqlite/jni/Tester1.java 214985f8a700ac56a174809c04196215fef5d7bb8b86e095d9d0abf436349ffb +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7268b37a32657798d01e116d639585cb5ed9f83b0760eb0416882bef79ffcb78 +F ext/jni/src/org/sqlite/jni/Tester1.java f6fcd218eb9d459866578d80b4223c15a2336af7fd52454c0be57bc855b1a892 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 9dd8b78419e19e88bc3fbff9bf200390b146b2461af2bb6b93d8467036619e33 -R bf3d1c30171f035a2002cb68468caf2a +P e0fa03135942cd2fe732a74510d380ba78ab230c452168e638f32b4aee04b3f7 +R 67866ade0f482ffd79944a87403dd96d U stephan -Z a910bd2897a92249409319f7eba80e4a +Z b7b857a7fcd9ef420d175786c42a22e5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1516c35375..6a5c31e951 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e0fa03135942cd2fe732a74510d380ba78ab230c452168e638f32b4aee04b3f7 \ No newline at end of file +586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff \ No newline at end of file From 57645b67fa790e1ec9293fa99166be8c90904722 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 10:49:47 +0000 Subject: [PATCH 060/148] Add a way to convert from standard UTF-8 to a Java string (JNI lacks this capability). FossilOrigin-Name: 07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262 --- ext/jni/src/c/sqlite3-jni.c | 87 ++++++++++++++++++------- ext/jni/src/org/sqlite/jni/Tester1.java | 8 ++- manifest | 14 ++-- manifest.uuid | 2 +- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 646ecab06c..377c0e3a9c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -343,12 +343,14 @@ struct JNIEnvCacheLine { //! The various refs to global classes might be cacheable a single // time globally. Information online seems inconsistent on that // point. - jclass globalClassObj /* global ref to java.lang.Object */; - jclass globalClassLong /* global ref to java.lang.Long */; - jclass globalClassString /* global ref to java.lang.String */; - jobject globalClassCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; - jmethodID ctorLong1 /* the Long(long) constructor */; - jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + struct { + jclass cObj /* global ref to java.lang.Object */; + jclass cLong /* global ref to java.lang.Long */; + jclass cString /* global ref to java.lang.String */; + jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + } g; jobject currentStmt /* Current Java sqlite3_stmt object being prepared, stepped, reset, or finalized. Needed for tracing, the @@ -536,6 +538,41 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ } } +/** + Uses the java.lang.String(byte[],Charset) constructor to create a + new String from UTF-8 string z. n is the number of bytes to + copy. If n<0 then sqlite3Strlen30() is used to calculate it. + + Returns NULL if z is NULL or on OOM, else returns a new jstring + owned by the caller. + + Sidebar: this is a painfully inefficient way to convert from + standard UTF-8 to a Java string, but JNI offers only algorithms for + working with MUTF-8, not UTF-8. +*/ +static jstring s3jni_string_from_utf8(JNIEnvCacheLine * const jc, + const char * const z, int n){ + jstring rv = NULL; + JNIEnv * const env = jc->env; + if( 0==n || (z && !z[0]) ){ + /* Fast-track the empty-string case. We could hypothetically do + this for any strings where n<4 and z is NUL-terminated and none + of z[0..3] are NUL bytes. */ + rv = (*env)->NewStringUTF(env, ""); + }else if( z ){ + jbyteArray jba; + if( n<0 ) n = sqlite3Strlen30(z); + jba = (*env)->NewByteArray(env, (jsize)n); + if( jba ){ + (*env)->SetByteArrayRegion(env, jba, 0, n, (jbyte const *)z); + rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, + jba, jc->g.oCharsetUtf8); + UNREF_L(jba); + } + } + return rv; +} + /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized @@ -570,19 +607,21 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ if(row->pNext) row->pNext->pPrev = row; S3Global.envCache.aHead = row; row->env = env; - row->globalClassObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + + /* Grab references to various global classes and objects... */ + row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); EXCEPTION_IS_FATAL("Error getting reference to Object class."); - row->globalClassLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); EXCEPTION_IS_FATAL("Error getting reference to Long class."); - row->ctorLong1 = (*env)->GetMethodID(env, row->globalClassLong, - "", "(J)V"); + row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, + "", "(J)V"); EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); - row->globalClassString = REF_G((*env)->FindClass(env,"java/lang/String")); + row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); EXCEPTION_IS_FATAL("Error getting reference to String class."); - row->ctorStringBA = - (*env)->GetMethodID(env, row->globalClassString, + row->g.ctorStringBA = + (*env)->GetMethodID(env, row->g.cString, "", "([BLjava/nio/charset/Charset;)V"); EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); @@ -594,7 +633,7 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", "Ljava/nio/charset/Charset;"); EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); - row->globalClassCharsetUtf8 = + row->g.oCharsetUtf8 = REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); } @@ -667,10 +706,10 @@ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ JNIEnv * const env = p->env; if(env){ int i; - UNREF_G(p->globalClassObj); - UNREF_G(p->globalClassLong); - UNREF_G(p->globalClassString); - UNREF_G(p->globalClassCharsetUtf8); + UNREF_G(p->g.cObj); + UNREF_G(p->g.cLong); + UNREF_G(p->g.cString); + UNREF_G(p->g.oCharsetUtf8); UNREF_G(p->currentStmt); #ifdef SQLITE_ENABLE_FTS5 UNREF_G(p->jFtsExt); @@ -1377,7 +1416,7 @@ static int udf_args(JNIEnv *env, *jArgv = 0; if(!jcx) goto error_oom; ja = (*env)->NewObjectArray(env, argc, - S3Global_JNIEnvCache_cache(env)->globalClassObj, + S3Global_JNIEnvCache_cache(env)->g.cObj, NULL); if(!ja) goto error_oom; for(i = 0; i < argc; ++i){ @@ -2553,16 +2592,14 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ int rc; switch(traceflag){ case SQLITE_TRACE_STMT: - /* This is not _quite_ right: we're converting to MUTF-8. It - should(?) suffice for purposes of tracing, though. */ - jX = (*env)->NewStringUTF(env, (const char *)pX); + jX = s3jni_string_from_utf8(jc, (const char *)pX, -1); if(!jX) return SQLITE_NOMEM; jP = jc->currentStmt; break; case SQLITE_TRACE_PROFILE: - jX = (*env)->NewObject(env, jc->globalClassLong, jc->ctorLong1, + jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, (jlong)*((sqlite3_int64*)pX)); - // hmm. It really is zero. + // hmm. ^^^ (*pX) really is zero. // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); jP = jc->currentStmt; if(!jP){ @@ -3547,7 +3584,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klaz } assert( 1 == S3Global.metrics.envCacheMisses ); assert( env == S3Global.envCache.aHead->env ); - assert( 0 != S3Global.envCache.aHead->globalClassObj ); + assert( 0 != S3Global.envCache.aHead->g.cObj ); for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 334484166c..2d1dcd4be8 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -683,6 +683,10 @@ public class Tester1 { private static void testTrace(){ final sqlite3 db = createNewDb(); final ValueHolder counter = new ValueHolder<>(0); + /* Ensure that characters outside of the UTF BMP survive the trip + from Java to sqlite3 and back to Java. (At no small efficiency + penalty.) */ + final String nonBmpChar = "😃"; sqlite3_trace_v2( db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE, @@ -694,6 +698,7 @@ public class Tester1 { affirm(pNative instanceof sqlite3_stmt); affirm(x instanceof String); //outln("TRACE_STMT sql = "+x); + affirm( ((String)x).indexOf(nonBmpChar) > 0 ); break; case SQLITE_TRACE_PROFILE: affirm(pNative instanceof sqlite3_stmt); @@ -716,7 +721,8 @@ public class Tester1 { return 0; } }); - execSql(db, "SELECT coalesce(null,null,null,'hi'); SELECT 'world'"); + execSql(db, "SELECT coalesce(null,null,'"+nonBmpChar+"'); "+ + "SELECT 'w"+nonBmpChar+"orld'"); affirm( 6 == counter.value ); sqlite3_close_v2(db); affirm( 7 == counter.value ); diff --git a/manifest b/manifest index bc94970f53..5cc0301b26 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_db_filename()\sand\s(closely\srelated)\s(A)\sadd\smany\smore\sdocs\sabout\sthe\sUTF-8/MUTF-8\sdiscrepancy\s(B)\sstart\sadding\sinternals\sto\senable\sus\sto\sperform\sthe\sstandard-UTF-8-to-Java\sconversion\sfrom\sC. -D 2023-08-06T10:14:53.465 +C Add\sa\sway\sto\sconvert\sfrom\sstandard\sUTF-8\sto\sa\sJava\sstring\s(JNI\slacks\sthis\scapability). +D 2023-08-06T10:49:47.843 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 88c18f2f1dd8064ca3d264bcda0df950b57bc0f5b9d8bfeb43bdd3f5be723ab8 +F ext/jni/src/c/sqlite3-jni.c 0d487a655b1fe60906d1df71f2b99b59c7644015be92ccd531ceefee596dec97 F ext/jni/src/c/sqlite3-jni.h 03c61c4f84c028169633392d7eb06caa6000e8bf3c0a3f7ac44e645dedbbfb9a F ext/jni/src/org/sqlite/jni/Authorizer.java 8dde03bbe50896d2f426240a4af4dcb6d98b655af84fe6ed86e637f5d5ac1fc8 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7268b37a32657798d01e116d639585cb5ed9f83b0760eb0416882bef79ffcb78 -F ext/jni/src/org/sqlite/jni/Tester1.java f6fcd218eb9d459866578d80b4223c15a2336af7fd52454c0be57bc855b1a892 +F ext/jni/src/org/sqlite/jni/Tester1.java ecc72fcba231f5dfd787fd5d62fac685e8cfc349f74d11245d19325643517bfd F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P e0fa03135942cd2fe732a74510d380ba78ab230c452168e638f32b4aee04b3f7 -R 67866ade0f482ffd79944a87403dd96d +P 586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff +R 8ee2983dae779c21de40b749b6e6f931 U stephan -Z b7b857a7fcd9ef420d175786c42a22e5 +Z c665b4e52180bf9728f0f7861f45043f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 6a5c31e951..ba35528220 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff \ No newline at end of file +07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262 \ No newline at end of file From 6a9364a518ecb5dfa5e17add8ed9e4579f865c2a Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 11:05:17 +0000 Subject: [PATCH 061/148] Remove doc outdated warning about sqlite3_trace_v2() JNI binding being incompatible with MUTF-8. Use new to-string capability to simplify Fts5ExtensionApi::xColumnText() JNI binding. FossilOrigin-Name: ebcfc2379be12f76a96f3605b734f406b3354d4c985062cdbfca0cf7e3f31379 --- ext/jni/src/c/sqlite3-jni.c | 67 ++++++++----------- ext/jni/src/c/sqlite3-jni.h | 2 +- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 15 +---- ext/jni/src/org/sqlite/jni/OutputPointer.java | 6 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 7 -- manifest | 20 +++--- manifest.uuid | 2 +- 7 files changed, 43 insertions(+), 76 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 377c0e3a9c..0e50e20f22 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1075,11 +1075,13 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, stores it in pFieldId. Fails fatally if the property is not found, as that presents a serious internal misuse. - Property lookups are cached on a per-class basis. + Property lookups are cached on a per-zClassName basis. Do not use + this routine with the same zClassName but different zTypeSig: it + will misbehave. */ -static void setupOutputPointer(JNIEnv * env, const char *zClassName, - const char *zTypeSig, - jobject jOut, jfieldID * pFieldId){ +static void setupOutputPointer(JNIEnv * const env, const char *zClassName, + const char * const zTypeSig, + jobject const jOut, jfieldID * const pFieldId){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); @@ -1087,6 +1089,7 @@ static void setupOutputPointer(JNIEnv * env, const char *zClassName, setter = cacheLine->fidValue; }else{ const jclass klazz = (*env)->GetObjectClass(env, jOut); + //MARKER(("%s => %s\n", zClassName, zTypeSig)); setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); if(cacheLine){ @@ -1099,7 +1102,7 @@ static void setupOutputPointer(JNIEnv * env, const char *zClassName, /* Sets the value property of the OutputPointer.Int32 jOut object to v. */ -static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ +static void setOutputInt32(JNIEnv * const env, jobject const jOut, int v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_Int32, "I", jOut, &setter); (*env)->SetIntField(env, jOut, setter, (jint)v); @@ -1109,40 +1112,34 @@ static void setOutputInt32(JNIEnv * env, jobject jOut, int v){ #ifdef SQLITE_ENABLE_FTS5 /* Sets the value property of the OutputPointer.Int64 jOut object to v. */ -static void setOutputInt64(JNIEnv * env, jobject jOut, jlong v){ +static void setOutputInt64(JNIEnv * const env, jobject const jOut, jlong v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_Int64, "J", jOut, &setter); (*env)->SetLongField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } +#if 0 /* Sets the value property of the OutputPointer.ByteArray jOut object to v. */ -static void setOutputByteArray(JNIEnv * env, jobject jOut, jbyteArray v){ +static void setOutputByteArray(JNIEnv * const env, jobject const jOut, + jbyteArray const v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_ByteArray, "[B", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); } -#if 0 +#endif /* Sets the value property of the OutputPointer.String jOut object to v. */ -static void setOutputString(JNIEnv * env, jobject jOut, jstring v){ +static void setOutputString(JNIEnv * const env, jobject const jOut, + jstring const v){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_String, "Ljava/lang/String", - jOut, &setter); + setupOutputPointer(env, S3ClassNames.OutputPointer_String, + "Ljava/lang/String;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); } -//! Bad: uses MUTF-8 encoding. -static void setOutputString2(JNIEnv * env, jobject jOut, const char * zStr){ - jstring const jStr = (*env)->NewStringUTF(env, zStr); - if(jStr){ - setOutputString(env, jOut, jStr); - UNREF_L(jStr); - } -} -#endif #endif /* SQLITE_ENABLE_FTS5 */ static int encodingTypeIsValid(int eTextRep){ @@ -3027,32 +3024,22 @@ JDECLFtsXA(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32) } JDECLFtsXA(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, - jobject jOutBA){ + jobject jOut){ Fts5ExtDecl; const char *pz = 0; int pn = 0; int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - /* Two problems here: - - 1) JNI doesn't give us a way to create strings from standard - UTF-8. We're converting the results to MUTF-8, which may - differ for exotic text. - - 2) JNI's NewStringUTF() (which treats its input as MUTF-8) does - not take a _length_ - it requires the string to be - NUL-terminated, which may not the case here. - - So we use a byte array and convert it to UTF-8 Java-side. - */ - jbyteArray const jba = (*env)->NewByteArray(env, (jint)pn); - if( jba ){ - (*env)->SetByteArrayRegion(env, jba, 0, (jint)pn, (const jbyte*)pz); - setOutputByteArray(env, jOutBA, jba); - UNREF_L(jba)/*jOutBA has a reference*/; - }else{ - rc = SQLITE_NOMEM; + JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + jstring jstr = pz ? s3jni_string_from_utf8(jc, pz, pn) : 0; + if( pz ){ + if( jstr ){ + setOutputString(env, jOut, jstr); + UNREF_L(jstr)/*jOut has a reference*/; + }else{ + rc = SQLITE_NOMEM; + } } } return (jint)rc; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index ba24cb391a..ea783afe50 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1675,7 +1675,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnSize /* * Class: org_sqlite_jni_Fts5ExtensionApi * Method: xColumnText - * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/ByteArray;)I + * Signature: (Lorg/sqlite/jni/Fts5Context;ILorg/sqlite/jni/OutputPointer/String;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_Fts5ExtensionApi_xColumnText (JNIEnv *, jobject, jobject, jint, jobject); diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 2667667d97..5f2e560daf 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -40,20 +40,7 @@ public final class Fts5ExtensionApi extends NativePointerHolder Date: Sun, 6 Aug 2023 13:02:43 +0000 Subject: [PATCH 062/148] JNI-internal cleanups and API renaming. Add a C-side java-string-to-utf8 conversion. FossilOrigin-Name: 672d85795d04131135b1dc6a02d64eceb8b5084217c17766afeca4af23c07ec4 --- ext/jni/GNUmakefile | 4 +- ext/jni/src/c/sqlite3-jni.c | 243 ++++++++++-------- ext/jni/src/c/sqlite3-jni.h | 6 +- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 2 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 19 +- ext/jni/src/org/sqlite/jni/Tester1.java | 2 +- ext/jni/src/org/sqlite/jni/fts5_api.java | 2 +- manifest | 24 +- manifest.uuid | 2 +- 9 files changed, 155 insertions(+), 149 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index d3b376ba0e..98ea80bb8d 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -128,7 +128,6 @@ $(sqlite3.c): $(sqlite3.h) SQLITE_OPT := \ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ - -DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \ -DSQLITE_ENABLE_STMTVTAB \ -DSQLITE_ENABLE_DBPAGE_VTAB \ -DSQLITE_ENABLE_DBSTAT_VTAB \ @@ -137,7 +136,6 @@ SQLITE_OPT := \ -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 \ @@ -147,7 +145,7 @@ SQLITE_OPT := \ # for a var which gets set in all builds but only read # via assert(). -SQLITE_OPFS += -g -DDEBUG -UNDEBUG +SQLITE_OPT += -g -DDEBUG -UNDEBUG ifeq (1,$(enable.fts5)) SQLITE_OPT += -DSQLITE_ENABLE_FTS5 diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 0e50e20f22..c2c99d8890 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -37,11 +37,9 @@ #if !defined(SQLITE_DEFAULT_PAGE_SIZE) # define SQLITE_DEFAULT_PAGE_SIZE 8192 #endif -#ifndef SQLITE_DEFAULT_UNIX_VFS -# define SQLITE_DEFAULT_UNIX_VFS "unix" +#ifndef SQLITE_DQS +# define SQLITE_DQS 0 #endif -#undef SQLITE_DQS -#define SQLITE_DQS 0 /**********************************************************************/ /* SQLITE_ENABLE_... */ @@ -57,11 +55,6 @@ #ifndef SQLITE_ENABLE_EXPLAIN_COMMENTS # define SQLITE_ENABLE_EXPLAIN_COMMENTS 1 #endif -#ifdef SQLITE_ENABLE_FTS5 -# ifndef SQLITE_ENABLE_FTS4 -# define SQLITE_ENABLE_FTS4 1 -# endif -#endif #ifndef SQLITE_ENABLE_MATH_FUNCTIONS # define SQLITE_ENABLE_MATH_FUNCTIONS 1 #endif @@ -74,15 +67,15 @@ #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif -#ifndef SQLITE_ENABLE_SESSION -# define SQLITE_ENABLE_SESSION 1 -#endif +//#ifndef SQLITE_ENABLE_SESSION +//# define SQLITE_ENABLE_SESSION 1 +//#endif #ifndef SQLITE_ENABLE_STMTVTAB # define SQLITE_ENABLE_STMTVTAB 1 #endif -#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION -# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION -#endif +//#ifndef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +//# define SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION +//#endif /**********************************************************************/ /* SQLITE_M... */ @@ -160,8 +153,8 @@ JFuncName(Suffix) /* First 2 parameters to all JNI bindings. */ #define JENV_JSELF JNIEnv * const env, jobject jSelf -/* Helpers to squelch -Xcheck:jni warnings about - not having checked for exceptions. */ +/* Helpers to account for -Xcheck:jni warnings about not having + checked for exceptions. */ #define IFTHREW if((*env)->ExceptionCheck(env)) #define EXCEPTION_IGNORE (void)((*env)->ExceptionCheck(env)) #define EXCEPTION_CLEAR (*env)->ExceptionClear(env) @@ -337,41 +330,41 @@ struct NphCacheLine { Whereas we cache new refs for each thread. */ -typedef struct JNIEnvCacheLine JNIEnvCacheLine; -struct JNIEnvCacheLine { +typedef struct JNIEnvCache JNIEnvCache; +struct JNIEnvCache { JNIEnv *env /* env in which this cache entry was created */; //! The various refs to global classes might be cacheable a single // time globally. Information online seems inconsistent on that // point. struct { - jclass cObj /* global ref to java.lang.Object */; - jclass cLong /* global ref to java.lang.Long */; - jclass cString /* global ref to java.lang.String */; - jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; - jmethodID ctorLong1 /* the Long(long) constructor */; - jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + jclass cObj /* global ref to java.lang.Object */; + jclass cLong /* global ref to java.lang.Long */; + jclass cString /* global ref to java.lang.String */; + jobject oCharsetUtf8 /* global ref to StandardCharset.UTF_8 */; + jmethodID ctorLong1 /* the Long(long) constructor */; + jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; + jmethodID stringGetBytes /* the String.getBytes(Charset) method */; } g; - jobject currentStmt /* Current Java sqlite3_stmt object being - prepared, stepped, reset, or - finalized. Needed for tracing, the - alternative being that we create a new - sqlite3_stmt wrapper object for every - tracing call which needs a stmt - object. This approach is rather invasive, - however, requiring code in all stmt - operations which can lead through the - tracing API. */; + jobject currentStmt /* Current Java sqlite3_stmt object being + prepared, stepped, reset, or + finalized. Needed for tracing, the + alternative being that we create a new + sqlite3_stmt wrapper object for every tracing + call which needs a stmt object. This approach + is rather invasive, however, requiring code + in all stmt operations which can lead through + the tracing API. */; #ifdef SQLITE_ENABLE_FTS5 - jobject jFtsExt /* Global ref to Java singleton for the - Fts5ExtensionApi instance. */; + jobject jFtsExt /* Global ref to Java singleton for the + Fts5ExtensionApi instance. */; struct { jclass klazz; jfieldID fidA; jfieldID fidB; } jPhraseIter; #endif - JNIEnvCacheLine * pPrev /* Previous entry in the linked list */; - JNIEnvCacheLine * pNext /* Next entry in the linked list */; + JNIEnvCache * pPrev /* Previous entry in the linked list */; + JNIEnvCache * pNext /* Next entry in the linked list */; /** TODO: NphCacheLine *pNphHit; to help fast-track cache lookups, update this to point to the @@ -445,8 +438,8 @@ static struct { */ JavaVM * jvm; struct { - JNIEnvCacheLine * aHead /* Linked list of in-use instances */; - JNIEnvCacheLine * aFree /* Linked list of free instances */; + JNIEnvCache * aHead /* Linked list of in-use instances */; + JNIEnvCache * aFree /* Linked list of free instances */; } envCache; struct { PerDbStateJni * aUsed /* Linked list of in-use instances */; @@ -550,14 +543,14 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ standard UTF-8 to a Java string, but JNI offers only algorithms for working with MUTF-8, not UTF-8. */ -static jstring s3jni_string_from_utf8(JNIEnvCacheLine * const jc, - const char * const z, int n){ +static jstring s3jni_utf8_to_jstring(JNIEnvCache * const jc, + const char * const z, int n){ jstring rv = NULL; JNIEnv * const env = jc->env; - if( 0==n || (z && !z[0]) ){ - /* Fast-track the empty-string case. We could hypothetically do - this for any strings where n<4 and z is NUL-terminated and none - of z[0..3] are NUL bytes. */ + if( 0==n || (n<0 && z && !z[0]) ){ + /* Fast-track the empty-string case via the MUTF-8 API. We could + hypothetically do this for any strings where n<4 and z is + NUL-terminated and none of z[0..3] are NUL bytes. */ rv = (*env)->NewStringUTF(env, ""); }else if( z ){ jbyteArray jba; @@ -573,6 +566,45 @@ static jstring s3jni_string_from_utf8(JNIEnvCacheLine * const jc, return rv; } +/** + Converts the given java.lang.String object into a NUL-terminated + UTF-8 C-string by calling jstr.getBytes(StandardCharset.UTF_8). + Returns NULL if jstr is NULL or on allocation error. If jstr is not + NULL and nLen is not NULL then nLen is set to the length of the + returned string, not including the terminating NUL. If jstr is not + NULL and it returns NULL, this indicates an allocation error. In + that case, if nLen is not NULL then it is either set to 0 (if + fetching of jstr's bytes fails to allocate) or set to what would + have been the length of the string had C-string allocation + succeeded. +*/ +static char * s3jni_jstring_to_utf8(JNIEnvCache * const jc, + jstring jstr, int *nLen){ + JNIEnv * const env = jc->env; + jbyteArray jba; + jsize nBa; + char *rv; + + if(!jstr) return 0; + jba = (*env)->CallObjectMethod(env, jstr, jc->g.stringGetBytes, + jc->g.oCharsetUtf8); + if( (*env)->ExceptionCheck(env) || !jba + /* order of these checks is significant for -Xlint:jni */ ) { + EXCEPTION_REPORT; + if( nLen ) *nLen = 0; + return 0; + } + nBa = (*env)->GetArrayLength(env, jba); + if( nLen ) *nLen = (int)nBa; + rv = sqlite3_malloc( nBa + 1 ); + if( rv ){ + (*env)->GetByteArrayRegion(env, jba, 0, nBa, (jbyte*)rv); + rv[nBa] = 0; + } + UNREF_L(jba); + return rv; +} + /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized @@ -580,8 +612,8 @@ static jstring s3jni_string_from_utf8(JNIEnvCacheLine * const jc, an entry fails. That's hypothetically possible but "shouldn't happen." */ FIXME_THREADING -static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ - struct JNIEnvCacheLine * row = S3Global.envCache.aHead; +static JNIEnvCache * S3Global_env_cache(JNIEnv * const env){ + struct JNIEnvCache * row = S3Global.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ ++S3Global.metrics.envCacheHits; @@ -595,7 +627,7 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ S3Global.envCache.aFree = row->pNext; if( row->pNext ) row->pNext->pPrev = 0; }else{ - row = sqlite3_malloc(sizeof(JNIEnvCacheLine)); + row = sqlite3_malloc(sizeof(JNIEnvCache)); if( !row ){ (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full.") /* Does not return, but cc doesn't know that */; @@ -624,6 +656,10 @@ static JNIEnvCacheLine * S3Global_JNIEnvCache_cache(JNIEnv * const env){ (*env)->GetMethodID(env, row->g.cString, "", "([BLjava/nio/charset/Charset;)V"); EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); + row->g.stringGetBytes = + (*env)->GetMethodID(env, row->g.cString, + "getBytes", "(Ljava/nio/charset/Charset;)[B"); + EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); { /* StandardCharsets.UTF_8 */ jfieldID fUtf8; @@ -702,7 +738,7 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ Requires that p has been snipped from any linked list it is in. Clears all Java refs p holds and zeroes out p. */ -static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ +static void JNIEnvCache_clear(JNIEnvCache * const p){ JNIEnv * const env = p->env; if(env){ int i; @@ -718,7 +754,7 @@ static void JNIEnvCacheLine_clear(JNIEnvCacheLine * const p){ for( i = 0; i < NphCache_SIZE; ++i ){ NphCacheLine_clear(env, &p->nph[i]); } - memset(p, 0, sizeof(JNIEnvCacheLine)); + memset(p, 0, sizeof(JNIEnvCache)); } } @@ -750,8 +786,8 @@ static void PerDbStateJni_free_for_env(JNIEnv *env){ Also passes env to PerDbStateJni_free_for_env() to free up what would otherwise be stale references. */ -static int S3Global_JNIEnvCache_uncache(JNIEnv * const env){ - struct JNIEnvCacheLine * row = S3Global.envCache.aHead; +static int S3Global_env_uncache(JNIEnv * const env){ + struct JNIEnvCache * row = S3Global.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ break; @@ -764,7 +800,7 @@ static int S3Global_JNIEnvCache_uncache(JNIEnv * const env){ assert( !row->pPrev ); S3Global.envCache.aHead = row->pNext; } - JNIEnvCacheLine_clear(row); + JNIEnvCache_clear(row); assert( !row->pNext ); assert( !row->pPrev ); row->pNext = S3Global.envCache.aFree; @@ -776,7 +812,7 @@ static int S3Global_JNIEnvCache_uncache(JNIEnv * const env){ static void S3Global_JNIEnvCache_clear(void){ while( S3Global.envCache.aHead ){ - S3Global_JNIEnvCache_uncache( S3Global.envCache.aHead->env ); + S3Global_env_uncache( S3Global.envCache.aHead->env ); } } @@ -813,7 +849,7 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char * looking up class objects can be expensive, so they should be cached as well. */ - struct JNIEnvCacheLine * const envRow = S3Global_JNIEnvCache_cache(env); + struct JNIEnvCache * const envRow = S3Global_env_cache(env); struct NphCacheLine * freeSlot = 0; struct NphCacheLine * cacheLine = 0; int i; @@ -1413,7 +1449,7 @@ static int udf_args(JNIEnv *env, *jArgv = 0; if(!jcx) goto error_oom; ja = (*env)->NewObjectArray(env, argc, - S3Global_JNIEnvCache_cache(env)->g.cObj, + S3Global_env_cache(env)->g.cObj, NULL); if(!ja) goto error_oom; for(i = 0; i < argc; ++i){ @@ -2063,41 +2099,25 @@ JDECL(jint,1create_1function)(JENV_JSELF, jobject jDb, jstring jFuncName, } -JDECL(jbyteArray,1db_1filename)(JENV_JSELF, jobject jDb, jbyteArray jDbName){ -#if 1 +JDECL(jstring,1db_1filename)(JENV_JSELF, jobject jDb, jstring jDbName){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); - jbyte *zFilename = (ps && jDbName) ? JBA_TOC(jDbName) : 0; - const char *zRv; - jbyteArray jRv = 0; + JNIEnvCache * const jc = S3Global_env_cache(env); + char *zDbName; + jstring jRv = 0; + int nStr = 0; - if( !ps || (jDbName && !zFilename) ) return 0; - zRv = sqlite3_db_filename(ps->pDb, (const char *)zFilename); - if( zRv ){ - const int n = sqlite3Strlen30(zRv); - jRv = (*env)->NewByteArray(env, (jint)n); - if( jRv ){ - (*env)->SetByteArrayRegion(env, jRv, 0, (jint)n, (const jbyte *)zRv); + if( !ps || !jDbName ){ + return 0; + } + zDbName = s3jni_jstring_to_utf8(jc, jDbName, &nStr); + if( zDbName ){ + char const * zRv = sqlite3_db_filename(ps->pDb, zDbName); + sqlite3_free(zDbName); + if( zRv ){ + jRv = s3jni_utf8_to_jstring(jc, zRv, -1); } } - JBA_RELEASE(jDbName, zFilename); return jRv; -#else - /* For comparison, this impl expects a jstring jDbName and returns a - jstring for significant code savings but it's not - MUTF-8-safe. With this impl, the Java-side byte-array-using - sqlite3_db_filename() impl is unnecessary. */ - JDECL(jstring,1db_1filename)(JENV_JSELF, jobject jDb, jstring jDbName){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); - const char *zFilename = (ps && jDbName) ? JSTR_TOC(jDbName) : 0; - const char *zRv; - - if( !ps || (jDbName && !zFilename)) return 0; - zRv = sqlite3_db_filename(ps->pDb, zFilename ? zFilename : "main"); - JSTR_RELEASE(jDbName, zFilename); - return zRv ? (*env)->NewStringUTF(env, zRv) : 0; -} -#endif - } JDECL(jstring,1errmsg)(JENV_JSELF, jobject jpDb){ @@ -2127,7 +2147,7 @@ JDECL(jint,1initialize)(JENV_JSELF){ is needed for certain tracing flags. At a minumum those ops are: step, reset, finalize, prepare. */ -static jobject stmt_set_current(JNIEnvCacheLine * const jc, jobject jStmt){ +static jobject stmt_set_current(JNIEnvCache * const jc, jobject jStmt){ jobject const old = jc->currentStmt; jc->currentStmt = jStmt; return old; @@ -2137,7 +2157,7 @@ JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_finalize(pStmt); setNativePointer(env, jpStmt, 0, S3ClassNames.sqlite3_stmt); @@ -2208,7 +2228,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass sqlite3_stmt * pStmt = 0; const char * zTail = 0; jbyte * const pBuf = JBA_TOC(baSql); - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); jobject const pOldStmt = stmt_set_current(jc, jOutStmt); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); @@ -2303,7 +2323,7 @@ JDECL(jint,1reset)(JENV_JSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_reset(pStmt); (void)stmt_set_current(jc, pPrev); @@ -2571,7 +2591,7 @@ JDECL(jint,1step)(JENV_JSELF,jobject jStmt){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); if(pStmt){ - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); jobject const jPrevStmt = stmt_set_current(jc, jStmt); rc = sqlite3_step(pStmt); (void)stmt_set_current(jc, jPrevStmt); @@ -2585,11 +2605,11 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ jobject jX = NULL /* the tracer's X arg */; jobject jP = NULL /* the tracer's P arg */; jobject jPUnref = NULL /* potentially a local ref to jP */; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); int rc; switch(traceflag){ case SQLITE_TRACE_STMT: - jX = s3jni_string_from_utf8(jc, (const char *)pX, -1); + jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); if(!jX) return SQLITE_NOMEM; jP = jc->currentStmt; break; @@ -2841,6 +2861,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); SO(JniHookState); + SO(JNIEnvCache); SO(PerDbStateJni); SO(S3Global); SO(S3ClassNames); @@ -2964,7 +2985,7 @@ static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ instance, or NULL on OOM. */ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ - JNIEnvCacheLine * const row = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const row = S3Global_env_cache(env); if( !row->jFtsExt ){ row->jFtsExt = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, s3jni_ftsext()); @@ -3031,8 +3052,8 @@ JDECLFtsXA(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); - jstring jstr = pz ? s3jni_string_from_utf8(jc, pz, pn) : 0; + JNIEnvCache * const jc = S3Global_env_cache(env); + jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; if( pz ){ if( jstr ){ setOutputString(env, jOut, jstr); @@ -3188,7 +3209,7 @@ JDECLFtsXA(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ /** Initializes jc->jPhraseIter if it needed it. */ -static void s3jni_phraseIter_init(JNIEnv *const env, JNIEnvCacheLine * const jc, +static void s3jni_phraseIter_init(JNIEnv *const env, JNIEnvCache * const jc, jobject jIter){ if(!jc->jPhraseIter.klazz){ jclass klazz = (*env)->GetObjectClass(env, jIter); @@ -3202,7 +3223,7 @@ static void s3jni_phraseIter_init(JNIEnv *const env, JNIEnvCacheLine * const jc, } /* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ -static void s3jni_phraseIter_NToJ(JNIEnv *const env, JNIEnvCacheLine const * const jc, +static void s3jni_phraseIter_NToJ(JNIEnv *const env, JNIEnvCache const * const jc, Fts5PhraseIter const * const pSrc, jobject jIter){ assert(jc->jPhraseIter.klazz); @@ -3213,7 +3234,7 @@ static void s3jni_phraseIter_NToJ(JNIEnv *const env, JNIEnvCacheLine const * con } /* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ -static void s3jni_phraseIter_JToN(JNIEnv *const env, JNIEnvCacheLine const * const jc, +static void s3jni_phraseIter_JToN(JNIEnv *const env, JNIEnvCache const * const jc, jobject jIter, Fts5PhraseIter * const pDest){ assert(jc->jPhraseIter.klazz); pDest->a = @@ -3228,7 +3249,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0, iOff = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3245,7 +3266,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3261,7 +3282,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); Fts5PhraseIter iter; int iCol = 0, iOff = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3276,7 +3297,7 @@ JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, JDECLFtsXA(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); Fts5PhraseIter iter; int iCol = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3298,7 +3319,7 @@ JDECLFtsXA(jint,xPhraseSize)(JENV_JSELF,jobject jCtx, jint iPhrase){ struct s3jni_xQueryPhraseState { JNIEnv *env; Fts5ExtensionApi const * fext; - JNIEnvCacheLine const * jc; + JNIEnvCache const * jc; jmethodID midCallback; jobject jCallback; jobject jFcx; @@ -3330,7 +3351,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, JDECLFtsXA(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; if( !klazz ){ @@ -3419,7 +3440,7 @@ static jint s3jni_fts5_xTokenize(JENV_JSELF, const char *zClassName, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; - JNIEnvCacheLine * const jc = S3Global_JNIEnvCache_cache(env); + JNIEnvCache * const jc = S3Global_env_cache(env); struct s3jni_xQueryPhraseState s; int rc = 0; jbyte * const pText = JBA_TOC(jbaText); @@ -3485,8 +3506,8 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ return pAux ? pAux->jUserData : 0; } - #endif /* SQLITE_ENABLE_FTS5 */ + //////////////////////////////////////////////////////////////////////// // End of the main API bindings. What follows are internal utilities. //////////////////////////////////////////////////////////////////////// @@ -3499,7 +3520,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ */ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JNIEnv * const env, jclass self){ - return S3Global_JNIEnvCache_uncache(env) ? JNI_TRUE : JNI_FALSE; + return S3Global_env_uncache(env) ? JNI_TRUE : JNI_FALSE; } @@ -3564,7 +3585,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klaz (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); return; } - (void)S3Global_JNIEnvCache_cache(env); + (void)S3Global_env_cache(env); if( !S3Global.envCache.aHead ){ (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); return; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index ea783afe50..0e5e0813f7 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1094,10 +1094,10 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_db_filename - * Signature: (Lorg/sqlite/jni/sqlite3;[B)[B + * Signature: (Lorg/sqlite/jni/sqlite3;Ljava/lang/String;)Ljava/lang/String; */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename - (JNIEnv *, jclass, jobject, jbyteArray); +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename + (JNIEnv *, jclass, jobject, jstring); /* * Class: org_sqlite_jni_SQLite3Jni diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 5f2e560daf..327e5a5d28 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -34,7 +34,7 @@ public final class Fts5ExtensionApi extends NativePointerHolder xDestroyed = new ValueHolder<>(false); final ValueHolder xBusyCalled = new ValueHolder<>(0); diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index 53afeb93c6..4df8a748e2 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -30,7 +30,7 @@ public final class fts5_api extends NativePointerHolder { Returns the fts5_api instance associated with the given db, or null if something goes horribly wrong. */ - public static native fts5_api getInstanceForDb(@NotNull sqlite3 db); + public static synchronized native fts5_api getInstanceForDb(@NotNull sqlite3 db); // int (*xCreateTokenizer)( // fts5_api *pApi, diff --git a/manifest b/manifest index 749ff0d902..4be312149d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sdoc\soutdated\swarning\sabout\ssqlite3_trace_v2()\sJNI\sbinding\sbeing\sincompatible\swith\sMUTF-8.\sUse\snew\sto-string\scapability\sto\ssimplify\sFts5ExtensionApi::xColumnText()\sJNI\sbinding. -D 2023-08-06T11:05:17.309 +C JNI-internal\scleanups\sand\sAPI\srenaming.\sAdd\sa\sC-side\sjava-string-to-utf8\sconversion. +D 2023-08-06T13:02:43.735 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,10 +230,10 @@ 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 bb4cd99bd8da534215cb6d278f05a626283eb5d2e8aebdb4d35e548637d35a9a +F ext/jni/GNUmakefile 9f0b0172903dfdb46ed3a77d431153238b7b96ec66cf40333c07e6dd23a3fabe F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 433ac7a2f113cda29ecf89b72711b55eeb97be5305fadd420408d1e538699177 -F ext/jni/src/c/sqlite3-jni.h 1bb138aa39a5ae6cc0b2ab6c72de9afe752123b02f3322a8d5b1ca36b9e5a410 +F ext/jni/src/c/sqlite3-jni.c 38c251d74f78b54b30e84ed97230eb2fa008e7400e9a460066ef6f1c43c06a2b +F ext/jni/src/c/sqlite3-jni.h 8ddf8a2e044d7880c75c07c9f025f3cdc5d486a42d30d99e0c45d7a8a973a97d F ext/jni/src/org/sqlite/jni/Authorizer.java 8dde03bbe50896d2f426240a4af4dcb6d98b655af84fe6ed86e637f5d5ac1fc8 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -241,7 +241,7 @@ F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java aaab15f6dd515fa45b99ddb1f2f889657a2939f64f7934114f013c98442032da +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 5b92cc034ca403936f11e07838699e6cf28afc5dd84020dfe9a6b64739b65065 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 @@ -250,13 +250,13 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java fcece068415b804aa7843534addb3905 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3494bd0eda06b56e137ff71fcdada9c463d40a0bbb02fc0ce96bf3761e6110b7 -F ext/jni/src/org/sqlite/jni/Tester1.java ecc72fcba231f5dfd787fd5d62fac685e8cfc349f74d11245d19325643517bfd +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java eb1a0b741654a1c813de4e3f11f354aae87b50f8656e440d95c0fd0509fad2f5 +F ext/jni/src/org/sqlite/jni/Tester1.java a57a56717104d6ff50932c9e81edf4a8773ce524d497988c8b044c18bbc963ee F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java ae52ff7f963976fabb7e87b0b8cdb3f9d2ba1838e7d3b79b0b4cb526202d4709 +F ext/jni/src/org/sqlite/jni/fts5_api.java 8c6b32455d7f85ee3f7f3e71c148bb3c2106f1d5484017daddfd560dd69d4f66 F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262 -R 774991bd316a19b5ee8ab2518b7f41ee +P ebcfc2379be12f76a96f3605b734f406b3354d4c985062cdbfca0cf7e3f31379 +R a7badfa2d93b121b26fb8275a69f64a6 U stephan -Z 78a283a26c39161657872bbb379dbda5 +Z afedd572770e60de292afd4c7121fd3f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 972cac53d6..7a14addf8c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ebcfc2379be12f76a96f3605b734f406b3354d4c985062cdbfca0cf7e3f31379 \ No newline at end of file +672d85795d04131135b1dc6a02d64eceb8b5084217c17766afeca4af23c07ec4 \ No newline at end of file From 5558624b4344934ae203f89774f5c6ce4ad7e32f Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 14:19:09 +0000 Subject: [PATCH 063/148] Bind sqlite3_config() to JNI for call variants taking (int,int*) and (int,const char *) variadic arguments. FossilOrigin-Name: 6119289da85ac0c83e2a7236d24bbfff22334d6cf1d852756dc658ad6a75dfec --- ext/jni/GNUmakefile | 24 +- ext/jni/src/c/sqlite3-jni.c | 308 +++++++++++++-------- ext/jni/src/c/sqlite3-jni.h | 16 ++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 17 ++ ext/jni/src/org/sqlite/jni/Tester1.java | 3 +- manifest | 20 +- manifest.uuid | 2 +- 7 files changed, 255 insertions(+), 135 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 98ea80bb8d..a1325434ae 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -153,17 +153,20 @@ endif 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 -ifeq (1,$(enable.fts5)) - sqlite3-jni.h.in += \ - $(dir.bld.c)/org_sqlite_jni_Fts5ExtensionApi.h \ - $(dir.bld.c)/org_sqlite_jni_fts5_api.h \ - $(dir.bld.c)/org_sqlite_jni_fts5_tokenizer.h -endif 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(). +# All javac-generated .h files must be listed in $(sqlite3-jni.h.in): +sqlite3-jni.h.in := +define ADD_JNI_H +sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h +$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).class +endef +$(eval $(call ADD_JNI_H,SQLite3Jni)) +ifeq (1,$(enable.fts5)) + $(eval $(call ADD_JNI_H,Fts5ExtensionApi)) + $(eval $(call ADD_JNI_H,fts5_api)) + $(eval $(call ADD_JNI_H,fts5_tokenizer)) +endif #sqlite3-jni.dll.cfiles := $(dir.src.c) sqlite3-jni.dll.cflags := \ -fPIC \ @@ -182,10 +185,9 @@ sqlite3-jni.dll.cflags := \ # include path for client-level code. ######################################################################## -$(sqlite3-jni.h.in): $(dir.src.jni)/SQLite3Jni.class $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) cat $(sqlite3-jni.h.in) > $@ -$(sqlite3-jni.c): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) +$(sqlite3-jni.dll): $(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 $@ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index c2c99d8890..52f166fe8a 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -151,8 +151,19 @@ #define JDECL(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ JFuncName(Suffix) -/* First 2 parameters to all JNI bindings. */ -#define JENV_JSELF JNIEnv * const env, jobject jSelf +/* First 2 parameters to all JNI bindings. + + Note that javac -h outputs jSelf as type jclass + but the docs: + + https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers + https://www3.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html + + say that's a jobject referring to the "this" for the call, but for + static methods it's actually emitted as jclass. +*/ +#define JENV_OSELF JNIEnv * const env, jobject jSelf +#define JENV_CSELF JNIEnv * const env, jclass jSelf /* Helpers to account for -Xcheck:jni warnings about not having checked for exceptions. */ #define IFTHREW if((*env)->ExceptionCheck(env)) @@ -228,13 +239,13 @@ static const struct { /** Create a trivial JNI wrapper for (int CName(void)). */ #define WRAP_INT_VOID(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JNIEnv *env, jobject jSelf){ \ + JDECL(jint,JniNameSuffix)(JENV_CSELF){ \ return (jint)CName(); \ } /** Create a trivial JNI wrapper for (int CName(int)). */ #define WRAP_INT_INT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JNIEnv *env, jobject jSelf, jint arg){ \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jint arg){ \ return (jint)CName((int)arg); \ } @@ -242,39 +253,39 @@ static const struct { CName(void)). This is only valid for functions which are known to return ASCII or text which is equivalent in UTF-8 and MUTF-8. */ #define WRAP_MUTF8_VOID(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_JSELF){ \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF){ \ return (*env)->NewStringUTF( env, CName() ); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*)). */ #define WRAP_INT_STMT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject jpStmt){ \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpStmt){ \ jint const rc = (jint)CName(PtrGet_sqlite3_stmt(jpStmt)); \ EXCEPTION_IGNORE /* squelch -Xcheck:jni */; \ return rc; \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_stmt*,int)). */ #define WRAP_INT_STMT_INT(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pStmt, jint n){ \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint n){ \ return (jint)CName(PtrGet_sqlite3_stmt(pStmt), (int)n); \ } /** Create a trivial JNI wrapper for (jstring CName(sqlite3_stmt*,int)). */ #define WRAP_STR_STMT_INT(JniNameSuffix,CName) \ - JDECL(jstring,JniNameSuffix)(JENV_JSELF, jobject pStmt, jint ndx){ \ + JDECL(jstring,JniNameSuffix)(JENV_CSELF, jobject pStmt, jint ndx){ \ return (*env)->NewStringUTF(env, CName(PtrGet_sqlite3_stmt(pStmt), (int)ndx)); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3*)). */ #define WRAP_INT_DB(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ return (jint)CName(PtrGet_sqlite3(pDb)); \ } /** Create a trivial JNI wrapper for (int64 CName(sqlite3*)). */ #define WRAP_INT64_DB(JniNameSuffix,CName) \ - JDECL(jlong,JniNameSuffix)(JENV_JSELF, jobject pDb){ \ + JDECL(jlong,JniNameSuffix)(JENV_CSELF, jobject pDb){ \ return (jlong)CName(PtrGet_sqlite3(pDb)); \ } /** Create a trivial JNI wrapper for (int CName(sqlite3_value*)). */ #define WRAP_INT_SVALUE(JniNameSuffix,CName) \ - JDECL(jint,JniNameSuffix)(JENV_JSELF, jobject jpSValue){ \ + JDECL(jint,JniNameSuffix)(JENV_CSELF, jobject jpSValue){ \ return (jint)CName(PtrGet_sqlite3_value(jpSValue)); \ } @@ -408,6 +419,8 @@ struct PerDbStateJni { it would be a different instance (and maybe even a different class) than the one the user may expect to receive. */; + char * zMainDbName /* Holds any string allocated on behave of + SQLITE_DBCONFIG_MAINDBNAME. */; JniHookState busyHandler; JniHookState collation; JniHookState collationNeeded; @@ -577,6 +590,9 @@ static jstring s3jni_utf8_to_jstring(JNIEnvCache * const jc, fetching of jstr's bytes fails to allocate) or set to what would have been the length of the string had C-string allocation succeeded. + + The returned memory is allocated from sqlite3_malloc() and + ownership is transferred to the caller. */ static char * s3jni_jstring_to_utf8(JNIEnvCache * const jc, jstring jstr, int *nLen){ @@ -710,6 +726,7 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ assert(!s->pPrev); S3Global.perDb.aUsed = s->pNext; } + sqlite3_free( s->zMainDbName ); #define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY) UNHOOK(trace, 0); UNHOOK(progress, 0); @@ -1613,7 +1630,7 @@ WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) -JDECL(jint,1bind_1blob)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ int rc; if(!baData){ @@ -1627,27 +1644,27 @@ JDECL(jint,1bind_1blob)(JENV_JSELF, jobject jpStmt, return (jint)rc; } -JDECL(jint,1bind_1double)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, jint ndx, jdouble val){ return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } -JDECL(jint,1bind_1int)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, jint ndx, jint val){ return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } -JDECL(jint,1bind_1int64)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong val){ return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } -JDECL(jint,1bind_1null)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -JDECL(jint,1bind_1parameter_1index)(JENV_JSELF, jobject jpStmt, jbyteArray jName){ +JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ int rc = 0; jbyte * const pBuf = JBA_TOC(jName); if(pBuf){ @@ -1658,7 +1675,7 @@ JDECL(jint,1bind_1parameter_1index)(JENV_JSELF, jobject jpStmt, jbyteArray jName return rc; } -JDECL(jint,1bind_1text)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ if(baData){ jbyte * const pBuf = JBA_TOC(baData); @@ -1671,12 +1688,12 @@ JDECL(jint,1bind_1text)(JENV_JSELF, jobject jpStmt, } } -JDECL(jint,1bind_1zeroblob)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, jint ndx, jint n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } -JDECL(jint,1bind_1zeroblob64)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); } @@ -1697,7 +1714,7 @@ static int s3jni_busy_handler(void* pState, int n){ return rc; } -JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ +JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); int rc = 0; if(!ps) return (jint)SQLITE_NOMEM; @@ -1726,7 +1743,7 @@ JDECL(jint,1busy_1handler)(JENV_JSELF, jobject jDb, jobject jBusy){ : sqlite3_busy_handler(ps->pDb, 0, 0); } -JDECL(jint,1busy_1timeout)(JENV_JSELF, jobject jDb, jint ms){ +JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); if( ps ){ JniHookState_unref(env, &ps->busyHandler, 1); @@ -1757,11 +1774,11 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ return (jint)rc; } -JDECL(jint,1close_1v2)(JENV_JSELF, jobject pDb){ +JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 2); } -JDECL(jint,1close)(JENV_JSELF, jobject pDb){ +JDECL(jint,1close)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 1); } @@ -1798,7 +1815,7 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, UNREF_L(jName); } -JDECL(jint,1collation_1needed)(JENV_JSELF, jobject jDb, jobject jHook){ +JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; @@ -1834,7 +1851,7 @@ JDECL(jint,1collation_1needed)(JENV_JSELF, jobject jDb, jobject jHook){ return rc; } -JDECL(jbyteArray,1column_1blob)(JENV_JSELF, jobject jpStmt, +JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); void const * const p = sqlite3_column_blob(pStmt, (int)ndx); @@ -1847,17 +1864,17 @@ JDECL(jbyteArray,1column_1blob)(JENV_JSELF, jobject jpStmt, } } -JDECL(jdouble,1column_1double)(JENV_JSELF, jobject jpStmt, +JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -JDECL(jint,1column_1int)(JENV_JSELF, jobject jpStmt, +JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -JDECL(jlong,1column_1int64)(JENV_JSELF, jobject jpStmt, +JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } @@ -1887,7 +1904,7 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * return jba; } -JDECL(jstring,1column_1text)(JENV_JSELF, jobject jpStmt, +JDECL(jstring,1column_1text)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes16(stmt, (int)ndx); @@ -1895,7 +1912,7 @@ JDECL(jstring,1column_1text)(JENV_JSELF, jobject jpStmt, return s3jni_text16_to_jstring(env, p, n); } -JDECL(jbyteArray,1column_1text_1utf8)(JENV_JSELF, jobject jpStmt, +JDECL(jbyteArray,1column_1text_1utf8)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes(stmt, (int)ndx); @@ -1903,7 +1920,7 @@ JDECL(jbyteArray,1column_1text_1utf8)(JENV_JSELF, jobject jpStmt, return s3jni_new_jbyteArray(env, p, n); } -JDECL(jobject,1column_1value)(JENV_JSELF, jobject jpStmt, +JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); return new_sqlite3_value_wrapper(env, sv); @@ -1983,16 +2000,16 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobje return pOld; } -JDECL(jobject,1commit_1hook)(JENV_JSELF,jobject jDb, jobject jHook){ +JDECL(jobject,1commit_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ return s3jni_commit_rollback_hook(1, env, jDb, jHook); } -JDECL(jstring,1compileoption_1get)(JENV_JSELF, jint n){ +JDECL(jstring,1compileoption_1get)(JENV_CSELF, jint n){ return (*env)->NewStringUTF( env, sqlite3_compileoption_get(n) ); } -JDECL(jboolean,1compileoption_1used)(JENV_JSELF, jstring name){ +JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ const char *zUtf8 = JSTR_TOC(name); const jboolean rc = 0==sqlite3_compileoption_used(zUtf8) ? JNI_FALSE : JNI_TRUE; @@ -2000,13 +2017,81 @@ JDECL(jboolean,1compileoption_1used)(JENV_JSELF, jstring name){ return rc; } -JDECL(jobject,1context_1db_1handle)(JENV_JSELF, jobject jpCx){ +/* sqlite3_db_config() for (int,const char *) */ +JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( + JENV_CSELF, jobject jDb, jint op, jstring jStr +){ + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + int rc; + char *zStr; + + switch( (ps && jStr) ? op : 0 ){ + case SQLITE_DBCONFIG_MAINDBNAME: + zStr = s3jni_jstring_to_utf8(S3Global_env_cache(env), jStr, 0); + if( zStr ){ + rc = sqlite3_db_config(ps->pDb, (int)op, zStr); + if( rc ){ + sqlite3_free( zStr ); + }else{ + sqlite3_free( ps->zMainDbName ); + ps->zMainDbName = zStr; + } + }else{ + rc = SQLITE_NOMEM; + } + break; + default: + rc = SQLITE_MISUSE; + } + return rc; +} + +/* sqlite3_db_config() for (int,int*) */ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2)( + JENV_CSELF, jobject jDb, jint op, jobject jOut +){ + PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + int rc; + + switch( ps ? op : 0 ){ + case SQLITE_DBCONFIG_ENABLE_FKEY: + case SQLITE_DBCONFIG_ENABLE_TRIGGER: + case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case SQLITE_DBCONFIG_ENABLE_QPSG: + case SQLITE_DBCONFIG_TRIGGER_EQP: + case SQLITE_DBCONFIG_RESET_DATABASE: + case SQLITE_DBCONFIG_DEFENSIVE: + case SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case SQLITE_DBCONFIG_DQS_DML: + case SQLITE_DBCONFIG_DQS_DDL: + case SQLITE_DBCONFIG_ENABLE_VIEW: + case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case SQLITE_DBCONFIG_STMT_SCANSTATUS: + case SQLITE_DBCONFIG_REVERSE_SCANORDER: { + int pOut = 0; + rc = sqlite3_db_config( ps->pDb, (int)op, &pOut ); + if( 0==rc && jOut ){ + setOutputInt32(env, jOut, pOut); + } + break; + } + default: + rc = SQLITE_MISUSE; + } + return rc; +} + +JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); PerDbStateJni * const ps = pDb ? PerDbStateJni_for_db(env, 0, pDb, 0) : 0; return ps ? ps->jDb : 0; } -JDECL(jint,1create_1collation)(JENV_JSELF, jobject jDb, +JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, jstring name, jint eTextRep, jobject oCollation){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); @@ -2093,13 +2178,12 @@ error_cleanup: return (jint)rc; } -JDECL(jint,1create_1function)(JENV_JSELF, jobject jDb, jstring jFuncName, +JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, jint nArg, jint eTextRep, jobject jFunctor){ return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); } - -JDECL(jstring,1db_1filename)(JENV_JSELF, jobject jDb, jstring jDbName){ +JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); JNIEnvCache * const jc = S3Global_env_cache(env); char *zDbName; @@ -2120,21 +2204,21 @@ JDECL(jstring,1db_1filename)(JENV_JSELF, jobject jDb, jstring jDbName){ return jRv; } -JDECL(jstring,1errmsg)(JENV_JSELF, jobject jpDb){ +JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ return (*env)->NewStringUTF(env, sqlite3_errmsg(PtrGet_sqlite3(jpDb))); } -JDECL(jstring,1errstr)(JENV_JSELF, jint rcCode){ +JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)); } -JDECL(jboolean,1extended_1result_1codes)(JENV_JSELF, jobject jpDb, +JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, jboolean onoff){ int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); return rc ? JNI_TRUE : JNI_FALSE; } -JDECL(jint,1initialize)(JENV_JSELF){ +JDECL(jint,1initialize)(JENV_CSELF){ return sqlite3_initialize(); } @@ -2153,7 +2237,7 @@ static jobject stmt_set_current(JNIEnvCache * const jc, jobject jStmt){ return old; } -JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ +JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ @@ -2167,7 +2251,7 @@ JDECL(jint,1finalize)(JENV_JSELF, jobject jpStmt){ } -JDECL(jlong,1last_1insert_1rowid)(JENV_JSELF, jobject jpDb){ +JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } @@ -2194,7 +2278,7 @@ static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jDb, int return theRc; } -JDECL(jint,1open)(JENV_JSELF, jstring strName, jobject jOut){ +JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ sqlite3 * pOut = 0; const char *zName = strName ? JSTR_TOC(strName) : 0; int nrc = sqlite3_open(zName, &pOut); @@ -2205,7 +2289,7 @@ JDECL(jint,1open)(JENV_JSELF, jstring strName, jobject jOut){ return (jint)nrc; } -JDECL(jint,1open_1v2)(JENV_JSELF, jstring strName, +JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, jobject jOut, jint flags, jstring strVfs){ sqlite3 * pOut = 0; const char *zName = strName ? JSTR_TOC(strName) : 0; @@ -2287,7 +2371,7 @@ static int s3jni_progress_handler_impl(void *pP){ return rc; } -JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress){ +JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ PerDbStateJni * ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; jmethodID xCallback; @@ -2319,7 +2403,7 @@ JDECL(void,1progress_1handler)(JENV_JSELF,jobject jDb, jint n, jobject jProgress } -JDECL(jint,1reset)(JENV_JSELF, jobject jpStmt){ +JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ @@ -2409,19 +2493,19 @@ static void result_blob_text(int asBlob, int as64, } } -JDECL(void,1result_1blob)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jint nMax){ +JDECL(void,1result_1blob)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ return result_blob_text(1, 0, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1blob64)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ +JDECL(void,1result_1blob64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax){ return result_blob_text(1, 1, 0, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1double)(JENV_JSELF, jobject jpCx, jdouble v){ +JDECL(void,1result_1double)(JENV_CSELF, jobject jpCx, jdouble v){ sqlite3_result_double(PtrGet_sqlite3_context(jpCx), v); } -JDECL(void,1result_1error)(JENV_JSELF, jobject jpCx, jbyteArray baMsg, +JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, int eTextRep){ const char * zUnspecified = "Unspecified error."; jsize const baLen = (*env)->GetArrayLength(env, baMsg); @@ -2447,27 +2531,27 @@ JDECL(void,1result_1error)(JENV_JSELF, jobject jpCx, jbyteArray baMsg, JBA_RELEASE(baMsg,pjBuf); } -JDECL(void,1result_1error_1code)(JENV_JSELF, jobject jpCx, jint v){ +JDECL(void,1result_1error_1code)(JENV_CSELF, jobject jpCx, jint v){ sqlite3_result_error_code(PtrGet_sqlite3_context(jpCx), v ? (int)v : SQLITE_ERROR); } -JDECL(void,1result_1error_1nomem)(JENV_JSELF, jobject jpCx){ +JDECL(void,1result_1error_1nomem)(JENV_CSELF, jobject jpCx){ sqlite3_result_error_nomem(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1error_1toobig)(JENV_JSELF, jobject jpCx){ +JDECL(void,1result_1error_1toobig)(JENV_CSELF, jobject jpCx){ sqlite3_result_error_toobig(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1int)(JENV_JSELF, jobject jpCx, jint v){ +JDECL(void,1result_1int)(JENV_CSELF, jobject jpCx, jint v){ sqlite3_result_int(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(void,1result_1int64)(JENV_JSELF, jobject jpCx, jlong v){ +JDECL(void,1result_1int64)(JENV_CSELF, jobject jpCx, jlong v){ sqlite3_result_int64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); } -JDECL(void,1result_1java_1object)(JENV_JSELF, jobject jpCx, jobject v){ +JDECL(void,1result_1java_1object)(JENV_CSELF, jobject jpCx, jobject v){ if(v){ ResultJavaVal * const rjv = ResultJavaVal_alloc(env, v); if(rjv){ @@ -2481,32 +2565,32 @@ JDECL(void,1result_1java_1object)(JENV_JSELF, jobject jpCx, jobject v){ } } -JDECL(void,1result_1null)(JENV_JSELF, jobject jpCx){ +JDECL(void,1result_1null)(JENV_CSELF, jobject jpCx){ sqlite3_result_null(PtrGet_sqlite3_context(jpCx)); } -JDECL(void,1result_1text)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jint nMax){ +JDECL(void,1result_1text)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jint nMax){ return result_blob_text(0, 0, SQLITE_UTF8, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1text64)(JENV_JSELF, jobject jpCx, jbyteArray jBa, jlong nMax, +JDECL(void,1result_1text64)(JENV_CSELF, jobject jpCx, jbyteArray jBa, jlong nMax, jint eTextRep){ return result_blob_text(0, 1, eTextRep, env, PtrGet_sqlite3_context(jpCx), jBa, nMax); } -JDECL(void,1result_1value)(JENV_JSELF, jobject jpCx, jobject jpSVal){ +JDECL(void,1result_1value)(JENV_CSELF, jobject jpCx, jobject jpSVal){ sqlite3_result_value(PtrGet_sqlite3_context(jpCx), PtrGet_sqlite3_value(jpSVal)); } -JDECL(void,1result_1zeroblob)(JENV_JSELF, jobject jpCx, jint v){ +JDECL(void,1result_1zeroblob)(JENV_CSELF, jobject jpCx, jint v){ sqlite3_result_zeroblob(PtrGet_sqlite3_context(jpCx), (int)v); } -JDECL(jint,1result_1zeroblob64)(JENV_JSELF, jobject jpCx, jlong v){ +JDECL(jint,1result_1zeroblob64)(JENV_CSELF, jobject jpCx, jlong v){ return (jint)sqlite3_result_zeroblob64(PtrGet_sqlite3_context(jpCx), (sqlite3_int64)v); } -JDECL(jobject,1rollback_1hook)(JENV_JSELF,jobject jDb, jobject jHook){ +JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ return s3jni_commit_rollback_hook(0, env, jDb, jHook); } @@ -2536,7 +2620,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, return rc; } -JDECL(jint,1set_1authorizer)(JENV_JSELF,jobject jDb, jobject jHook){ +JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); JniHookState * const pHook = ps ? &ps->authHook : 0; @@ -2575,19 +2659,19 @@ JDECL(jint,1set_1authorizer)(JENV_JSELF,jobject jDb, jobject jHook){ } -JDECL(void,1set_1last_1insert_1rowid)(JENV_JSELF, jobject jpDb, jlong rowId){ +JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ sqlite3_set_last_insert_rowid(PtrGet_sqlite3_context(jpDb), (sqlite3_int64)rowId); } -JDECL(jint,1shutdown)(JENV_JSELF){ +JDECL(jint,1shutdown)(JENV_CSELF){ S3Global_JNIEnvCache_clear(); /* Do not clear S3Global.jvm: it's legal to call sqlite3_initialize() again to restart the lib. */ return sqlite3_shutdown(); } -JDECL(jint,1step)(JENV_JSELF,jobject jStmt){ +JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); if(pStmt){ @@ -2655,7 +2739,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ return rc; } -JDECL(jint,1trace_1v2)(JENV_JSELF,jobject jDb, jint traceMask, jobject jTracer){ +JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; if( !traceMask || !jTracer ){ @@ -2706,7 +2790,7 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, } -JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ +JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; @@ -2753,7 +2837,7 @@ JDECL(jobject,1update_1hook)(JENV_JSELF, jobject jDb, jobject jHook){ } -JDECL(jbyteArray,1value_1blob)(JENV_JSELF, jobject jpSVal){ +JDECL(jbyteArray,1value_1blob)(JENV_CSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const nLen = sqlite3_value_bytes(sv); const jbyte * pBytes = sqlite3_value_blob(sv); @@ -2767,41 +2851,41 @@ JDECL(jbyteArray,1value_1blob)(JENV_JSELF, jobject jpSVal){ } -JDECL(jdouble,1value_1double)(JENV_JSELF, jobject jpSVal){ +JDECL(jdouble,1value_1double)(JENV_CSELF, jobject jpSVal){ return (jdouble) sqlite3_value_double(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jobject,1value_1dup)(JENV_JSELF, jobject jpSVal){ +JDECL(jobject,1value_1dup)(JENV_CSELF, jobject jpSVal){ sqlite3_value * const sv = sqlite3_value_dup(PtrGet_sqlite3_value(jpSVal)); return sv ? new_sqlite3_value_wrapper(env, sv) : 0; } -JDECL(void,1value_1free)(JENV_JSELF, jobject jpSVal){ +JDECL(void,1value_1free)(JENV_CSELF, jobject jpSVal){ sqlite3_value_free(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jint,1value_1int)(JENV_JSELF, jobject jpSVal){ +JDECL(jint,1value_1int)(JENV_CSELF, jobject jpSVal){ return (jint) sqlite3_value_int(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jlong,1value_1int64)(JENV_JSELF, jobject jpSVal){ +JDECL(jlong,1value_1int64)(JENV_CSELF, jobject jpSVal){ return (jlong) sqlite3_value_int64(PtrGet_sqlite3_value(jpSVal)); } -JDECL(jobject,1value_1java_1object)(JENV_JSELF, jobject jpSVal){ +JDECL(jobject,1value_1java_1object)(JENV_CSELF, jobject jpSVal){ ResultJavaVal * const rv = sqlite3_value_pointer(PtrGet_sqlite3_value(jpSVal), RESULT_JAVA_VAL_STRING); return rv ? rv->jObj : NULL; } -JDECL(jstring,1value_1text)(JENV_JSELF, jobject jpSVal){ +JDECL(jstring,1value_1text)(JENV_CSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const n = sqlite3_value_bytes16(sv); const void * const p = sqlite3_value_text16(sv); return s3jni_text16_to_jstring(env, p, n); } -JDECL(jbyteArray,1value_1text_1utf8)(JENV_JSELF, jobject jpSVal){ +JDECL(jbyteArray,1value_1text_1utf8)(JENV_CSELF, jobject jpSVal){ sqlite3_value * const sv = PtrGet_sqlite3_value(jpSVal); int const n = sqlite3_value_bytes(sv); const unsigned char * const p = sqlite3_value_text(sv); @@ -2835,19 +2919,19 @@ static jbyteArray value_text16(int mode, JNIEnv * const env, jobject jpSVal){ return jba; } -JDECL(jbyteArray,1value_1text16)(JENV_JSELF, jobject jpSVal){ +JDECL(jbyteArray,1value_1text16)(JENV_CSELF, jobject jpSVal){ return value_text16(SQLITE_UTF16, env, jpSVal); } -JDECL(jbyteArray,1value_1text16le)(JENV_JSELF, jobject jpSVal){ +JDECL(jbyteArray,1value_1text16le)(JENV_CSELF, jobject jpSVal){ return value_text16(SQLITE_UTF16LE, env, jpSVal); } -JDECL(jbyteArray,1value_1text16be)(JENV_JSELF, jobject jpSVal){ +JDECL(jbyteArray,1value_1text16be)(JENV_CSELF, jobject jpSVal){ return value_text16(SQLITE_UTF16BE, env, jpSVal); } -JDECL(void,1do_1something_1for_1developer)(JENV_JSELF){ +JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ MARKER(("\nVarious bits of internal info:\n")); puts("FTS5 is " #ifdef SQLITE_ENABLE_FTS5 @@ -3010,7 +3094,7 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ return pRet; } -JDECLFtsApi(jobject,getInstanceForDb)(JENV_JSELF,jobject jDb){ +JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); jobject rv = 0; if(!ps) return 0; @@ -3027,16 +3111,16 @@ JDECLFtsApi(jobject,getInstanceForDb)(JENV_JSELF,jobject jDb){ } -JDECLFtsXA(jobject,getInstance)(JENV_JSELF){ +JDECLFtsXA(jobject,getInstance)(JENV_CSELF){ return s3jni_getFts5ExensionApi(env); } -JDECLFtsXA(jint,xColumnCount)(JENV_JSELF,jobject jCtx){ +JDECLFtsXA(jint,xColumnCount)(JENV_OSELF,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xColumnCount(PtrGet_Fts5Context(jCtx)); } -JDECLFtsXA(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32){ +JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32){ Fts5ExtDecl; int n1 = 0; int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); @@ -3044,7 +3128,7 @@ JDECLFtsXA(jint,xColumnSize)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOut32) return rc; } -JDECLFtsXA(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, +JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut){ Fts5ExtDecl; const char *pz = 0; @@ -3066,7 +3150,7 @@ JDECLFtsXA(jint,xColumnText)(JENV_JSELF,jobject jCtx, jint iCol, return (jint)rc; } -JDECLFtsXA(jint,xColumnTotalSize)(JENV_JSELF,jobject jCtx, jint iCol, jobject jOut64){ +JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); @@ -3116,7 +3200,7 @@ error_oom: return; } -JDECLFtsApi(jint,xCreateFunction)(JENV_JSELF, jstring jName, +JDECLFtsApi(jint,xCreateFunction)(JENV_OSELF, jstring jName, jobject jUserData, jobject jFunc){ fts5_api * const pApi = PtrGet_fts5_api(jSelf); int rc; @@ -3161,7 +3245,7 @@ static void s3jni_fts5AuxData_xDestroy(void *x){ } } -JDECLFtsXA(jobject,xGetAuxdata)(JENV_JSELF,jobject jCtx, jboolean bClear){ +JDECLFtsXA(jobject,xGetAuxdata)(JENV_OSELF,jobject jCtx, jboolean bClear){ Fts5ExtDecl; jobject rv = 0; s3jni_fts5AuxData * const pAux = fext->xGetAuxdata(PtrGet_Fts5Context(jCtx), bClear); @@ -3180,7 +3264,7 @@ JDECLFtsXA(jobject,xGetAuxdata)(JENV_JSELF,jobject jCtx, jboolean bClear){ return rv; } -JDECLFtsXA(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, +JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; int n1 = 0, n2 = 2, n3 = 0; @@ -3193,7 +3277,7 @@ JDECLFtsXA(jint,xInst)(JENV_JSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, return rc; } -JDECLFtsXA(jint,xInstCount)(JENV_JSELF,jobject jCtx, jobject jOut32){ +JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ Fts5ExtDecl; int nOut = 0; int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); @@ -3201,7 +3285,7 @@ JDECLFtsXA(jint,xInstCount)(JENV_JSELF,jobject jCtx, jobject jOut32){ return (jint)rc; } -JDECLFtsXA(jint,xPhraseCount)(JENV_JSELF,jobject jCtx){ +JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ Fts5ExtDecl; return (jint)fext->xPhraseCount(PtrGet_Fts5Context(jCtx)); } @@ -3245,7 +3329,7 @@ static void s3jni_phraseIter_JToN(JNIEnv *const env, JNIEnvCache const * const j EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.b field."); } -JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, +JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; @@ -3263,7 +3347,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_JSELF,jobject jCtx, jint iPhrase, return rc; } -JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, +JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; JNIEnvCache * const jc = S3Global_env_cache(env); @@ -3279,7 +3363,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_JSELF,jobject jCtx, jint iPhrase, return rc; } -JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, +JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; JNIEnvCache * const jc = S3Global_env_cache(env); @@ -3294,7 +3378,7 @@ JDECLFtsXA(void,xPhraseNext)(JENV_JSELF,jobject jCtx, jobject jIter, s3jni_phraseIter_NToJ(env, jc, &iter, jIter); } -JDECLFtsXA(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, +JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; JNIEnvCache * const jc = S3Global_env_cache(env); @@ -3308,7 +3392,7 @@ JDECLFtsXA(void,xPhraseNextColumn)(JENV_JSELF,jobject jCtx, jobject jIter, } -JDECLFtsXA(jint,xPhraseSize)(JENV_JSELF,jobject jCtx, jint iPhrase){ +JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ Fts5ExtDecl; return (jint)fext->xPhraseSize(PtrGet_Fts5Context(jCtx), (int)iPhrase); } @@ -3348,7 +3432,7 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, return rc; } -JDECLFtsXA(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, +JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; JNIEnvCache * const jc = S3Global_env_cache(env); @@ -3372,7 +3456,7 @@ JDECLFtsXA(jint,xQueryPhrase)(JENV_JSELF,jobject jFcx, jint iPhrase, } -JDECLFtsXA(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ +JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); @@ -3380,12 +3464,12 @@ JDECLFtsXA(jint,xRowCount)(JENV_JSELF,jobject jCtx, jobject jOut64){ return (jint)rc; } -JDECLFtsXA(jlong,xRowid)(JENV_JSELF,jobject jCtx){ +JDECLFtsXA(jlong,xRowid)(JENV_OSELF,jobject jCtx){ Fts5ExtDecl; return (jlong)fext->xRowid(PtrGet_Fts5Context(jCtx)); } -JDECLFtsXA(int,xSetAuxdata)(JENV_JSELF,jobject jCtx, jobject jAux){ +JDECLFtsXA(int,xSetAuxdata)(JENV_OSELF,jobject jCtx, jobject jAux){ Fts5ExtDecl; int rc; s3jni_fts5AuxData * pAux; @@ -3436,7 +3520,7 @@ static int s3jni_xTokenize_xToken(void *p, int tFlags, const char* z, /** Proxy for Fts5ExtensionApi.xTokenize() and fts5_tokenizer.xTokenize() */ -static jint s3jni_fts5_xTokenize(JENV_JSELF, const char *zClassName, +static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; @@ -3487,20 +3571,20 @@ static jint s3jni_fts5_xTokenize(JENV_JSELF, const char *zClassName, return (jint)rc; } -JDECLFtsXA(jint,xTokenize)(JENV_JSELF,jobject jFcx, jbyteArray jbaText, +JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, jobject jCallback){ return s3jni_fts5_xTokenize(env, jSelf, S3ClassNames.Fts5ExtensionApi, 0, jFcx, jbaText, jCallback); } -JDECLFtsTok(jint,xTokenize)(JENV_JSELF,jobject jFcx, jint tokFlags, +JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, jbyteArray jbaText, jobject jCallback){ return s3jni_fts5_xTokenize(env, jSelf, S3ClassNames.Fts5Tokenizer, tokFlags, jFcx, jbaText, jCallback); } -JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ +JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ Fts5ExtDecl; Fts5JniAux * const pAux = fext->xUserData(PtrGet_Fts5Context(jFcx)); return pAux ? pAux->jUserData : 0; @@ -3519,7 +3603,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_JSELF,jobject jFcx){ function be declared as synchronous. */ JNIEXPORT jboolean JNICALL -Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JNIEnv * const env, jclass self){ +Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ return S3Global_env_uncache(env) ? JNI_TRUE : JNI_FALSE; } @@ -3532,7 +3616,7 @@ Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JNIEnv * const env, jclass self){ sqlite3.c instead of sqlite3.h. */ JNIEXPORT void JNICALL -Java_org_sqlite_jni_SQLite3Jni_init(JNIEnv * const env, jclass self, jclass klazzSjni){ +Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF, jclass klazzSjni){ enum JType { JTYPE_INT, JTYPE_BOOL diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 0e5e0813f7..cb42dee577 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1099,6 +1099,22 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1data_1count JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename (JNIEnv *, jclass, jobject, jstring); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2 + (JNIEnv *, jclass, jobject, jint, jobject); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_config + * Signature: (Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2 + (JNIEnv *, jclass, jobject, jint, jstring); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_errcode diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 06d168464d..d474f54bf3 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -401,6 +401,23 @@ public final class SQLite3Jni { public static native String sqlite3_db_filename(@NotNull sqlite3 db, @NotNull String dbName); + /** + Overload for sqlite3_db_config() calls which take (int,int*) + variadic arguments. Returns SQLITE_MISUSE if op is not one of the + SQLITE_DBCONFIG_... options which uses this call form. + */ + public static native int sqlite3_db_config(@NotNull sqlite3 db, int op, + @Nullable OutputPointer.Int32 out); + /** + Overload for sqlite3_db_config() calls which take (int,const + char*) variadic arguments. As of SQLite3 v3.43 the only such + option is SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op + is not SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be + extended in future versions. + */ + public static native int sqlite3_db_config(@NotNull sqlite3 db, int op, + @NotNull String mainDbName); + public static native int sqlite3_errcode(@NotNull sqlite3 db); public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 38446e9c70..d25d879173 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -740,7 +740,8 @@ public class Tester1 { rc = sqlite3_open(dbName, db2); ++metrics.dbOpen; affirm( 0 == rc ); - affirm( sqlite3_db_filename(db1, "main").endsWith(dbName) ); + rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); + affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); final ValueHolder xDestroyed = new ValueHolder<>(false); final ValueHolder xBusyCalled = new ValueHolder<>(0); diff --git a/manifest b/manifest index 4be312149d..1fa307a0d9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI-internal\scleanups\sand\sAPI\srenaming.\sAdd\sa\sC-side\sjava-string-to-utf8\sconversion. -D 2023-08-06T13:02:43.735 +C Bind\ssqlite3_config()\sto\sJNI\sfor\scall\svariants\staking\s(int,int*)\sand\s(int,const\schar\s*)\svariadic\sarguments. +D 2023-08-06T14:19:09.966 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,10 +230,10 @@ 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 9f0b0172903dfdb46ed3a77d431153238b7b96ec66cf40333c07e6dd23a3fabe +F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c 38c251d74f78b54b30e84ed97230eb2fa008e7400e9a460066ef6f1c43c06a2b -F ext/jni/src/c/sqlite3-jni.h 8ddf8a2e044d7880c75c07c9f025f3cdc5d486a42d30d99e0c45d7a8a973a97d +F ext/jni/src/c/sqlite3-jni.c d930960da788dda2157a07b87e957efaba582b2bcedea8a778618d52c44f465a +F ext/jni/src/c/sqlite3-jni.h 58678453c1b6cccb8d728a60f59b2b0819226658376c0b36e025ce8b0fea75c3 F ext/jni/src/org/sqlite/jni/Authorizer.java 8dde03bbe50896d2f426240a4af4dcb6d98b655af84fe6ed86e637f5d5ac1fc8 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -250,8 +250,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java fcece068415b804aa7843534addb3905 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java eb1a0b741654a1c813de4e3f11f354aae87b50f8656e440d95c0fd0509fad2f5 -F ext/jni/src/org/sqlite/jni/Tester1.java a57a56717104d6ff50932c9e81edf4a8773ce524d497988c8b044c18bbc963ee +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java eb49c9e5424e80ed1465c184086b3895155eee0828fc1992bfee24b741735638 +F ext/jni/src/org/sqlite/jni/Tester1.java 04c43f3ec93b362fc1c66430a3125067285259cd4fa5afccdb2fa66b691db8d0 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ebcfc2379be12f76a96f3605b734f406b3354d4c985062cdbfca0cf7e3f31379 -R a7badfa2d93b121b26fb8275a69f64a6 +P 672d85795d04131135b1dc6a02d64eceb8b5084217c17766afeca4af23c07ec4 +R 954630e57c0a98665c12d4c05402ba1d U stephan -Z afedd572770e60de292afd4c7121fd3f +Z 20242c8fd8e36b24bc1995a50f9f99ca # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7a14addf8c..c629d39beb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -672d85795d04131135b1dc6a02d64eceb8b5084217c17766afeca4af23c07ec4 \ No newline at end of file +6119289da85ac0c83e2a7236d24bbfff22334d6cf1d852756dc658ad6a75dfec \ No newline at end of file From 1a2563a950c66abcb525266e268d6e384163aebb Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 15:01:38 +0000 Subject: [PATCH 064/148] Doc cleanups. FossilOrigin-Name: 5f56b007704f2aad4cbc6f0ccd1e1f1c974865971f99451352714ee7e077c284 --- ext/jni/src/c/sqlite3-jni.c | 17 ++++++++++------- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 7 +++---- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 52f166fe8a..e7422bfac8 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -151,16 +151,19 @@ #define JDECL(ReturnType,Suffix) \ JNIEXPORT ReturnType JNICALL \ JFuncName(Suffix) -/* First 2 parameters to all JNI bindings. +/** + Shortcuts for the first 2 parameters to all JNI bindings. - Note that javac -h outputs jSelf as type jclass - but the docs: + The type of the jSelf arg differs, but no docs seem to mention + this: for static methods it's of type jclass and for non-static + it's jobject. jobject actually works for all funcs, in the sense + that it compiles and runs so long as we don't use jSelf (which is + only rarely needed in this code), but to be pedantically correct we + need the proper type in the signature. + + Not even the official docs mention this discrepancy: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers - https://www3.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html - - say that's a jobject referring to the "this" for the call, but for - static methods it's actually emitted as jclass. */ #define JENV_OSELF JNIEnv * const env, jobject jSelf #define JENV_CSELF JNIEnv * const env, jclass jSelf diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 327e5a5d28..15077acdb4 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -15,24 +15,23 @@ package org.sqlite.jni; import java.nio.charset.StandardCharsets; /** - COMPLETELY UNTESTED. + ALMOST COMPLETELY UNTESTED. FAR FROM COMPLETE and the feasibility of binding this to Java is still undetermined. This might be removed. - - Reminder to self: the native Fts5ExtensionApi is a singleton. */ public final class Fts5ExtensionApi extends NativePointerHolder { //! Only called from JNI private Fts5ExtensionApi(){} private int iVersion = 2; + /* Callback type for used by xQueryPhrase(). */ public static interface xQueryPhraseCallback { int xCallback(Fts5ExtensionApi fapi, Fts5Context cx); } /** - Returns a singleton instance of this class. + Returns the singleton instance of this class. */ public static synchronized native Fts5ExtensionApi getInstance(); diff --git a/manifest b/manifest index 1fa307a0d9..caa0a2ffa8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_config()\sto\sJNI\sfor\scall\svariants\staking\s(int,int*)\sand\s(int,const\schar\s*)\svariadic\sarguments. -D 2023-08-06T14:19:09.966 +C Doc\scleanups. +D 2023-08-06T15:01:38.351 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md 6ff7e1f4100dee980434a6ee37a199b653bceec62e233a6e2ccde6e7ae0c58bf -F ext/jni/src/c/sqlite3-jni.c d930960da788dda2157a07b87e957efaba582b2bcedea8a778618d52c44f465a +F ext/jni/src/c/sqlite3-jni.c ec7de3a37d7a62598179bc602c2f81f28434264e9d2d62d894c8c4eb41098291 F ext/jni/src/c/sqlite3-jni.h 58678453c1b6cccb8d728a60f59b2b0819226658376c0b36e025ce8b0fea75c3 F ext/jni/src/org/sqlite/jni/Authorizer.java 8dde03bbe50896d2f426240a4af4dcb6d98b655af84fe6ed86e637f5d5ac1fc8 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -241,7 +241,7 @@ F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 5b92cc034ca403936f11e07838699e6cf28afc5dd84020dfe9a6b64739b65065 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 9798c6288097f4619ded680fe0961132a3f3d3cbffd7ce22096159f114f28c61 F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 672d85795d04131135b1dc6a02d64eceb8b5084217c17766afeca4af23c07ec4 -R 954630e57c0a98665c12d4c05402ba1d +P 6119289da85ac0c83e2a7236d24bbfff22334d6cf1d852756dc658ad6a75dfec +R 48b7c494d85b3192affc08b2af78551a U stephan -Z 20242c8fd8e36b24bc1995a50f9f99ca +Z a195dbc9574f42748d3f2a6c1a7b8425 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c629d39beb..7715cf2b16 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6119289da85ac0c83e2a7236d24bbfff22334d6cf1d852756dc658ad6a75dfec \ No newline at end of file +5f56b007704f2aad4cbc6f0ccd1e1f1c974865971f99451352714ee7e077c284 \ No newline at end of file From af90dcf3249d4b932d1e123f4f0f348962fcb2fd Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 20:01:30 +0000 Subject: [PATCH 065/148] Start adding JNI-side support for auto extensions, but hit a brick wall which requires slightly awkward semantics changes in the JNI bindings for sqlite3_open(_v2)() to resolve, so stash this #if'd out for the time being. FossilOrigin-Name: 77a32d238e80fe1d237768d88780043a7bd2b3543e6672536254782cbea0039c --- ext/jni/README.md | 11 ++- ext/jni/src/c/sqlite3-jni.c | 99 +++++++++++++++++++ ext/jni/src/c/sqlite3-jni.h | 2 +- ext/jni/src/org/sqlite/jni/Authorizer.java | 2 +- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 12 +-- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 22 ++++- manifest | 22 ++--- manifest.uuid | 2 +- 8 files changed, 146 insertions(+), 26 deletions(-) diff --git a/ext/jni/README.md b/ext/jni/README.md index a1a1dadc8b..5b985a0550 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -73,11 +73,12 @@ 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. +exceptions_ unless _very explicitly documented_ as being +throw-safe. Exceptions are generally 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) ------------------------------------------------------------------------ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index e7422bfac8..6a490404f7 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -393,6 +393,39 @@ static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ memset(p, 0, sizeof(NphCacheLine)); } +#define S3JNI_ENABLE_AUTOEXT 0 +#if S3JNI_ENABLE_AUTOEXT +/* + Whether auto extensions are feasible here is currently unknown due + to... + + 1) JNIEnv/threading issues. A db instance is mapped to a specific + JNIEnv object but auto extensions may be added from any thread. In + such contexts, which JNIEnv do we use for the JNI APIs? + + 2) a chicken/egg problem involving the Java/C mapping of the db: + when auto extensions are run, the db has not yet been connected to + Java. If we do that during the auto-ext, sqlite3_open(_v2)() will not behave + properly because they have a different jobject and the API + guarantees the user that _that_ object is the one the API will bind + the native to. + + If we change the open(_v2()) interfaces to use OutputPointer.sqlite3 + instead of the client passing in an instance, we could work around + (2). +*/ +typedef struct S3JniAutoExtension S3JniAutoExtension; +typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); +struct S3JniAutoExtension { + JNIEnv * env; + jobject jObj; + jmethodID midFunc; + S3JniAutoExtension_xEntryPoint xEntryPoint; + S3JniAutoExtension *pNext /* next linked-list entry */; + S3JniAutoExtension *pPrev /* previous linked-list entry */; +}; +#endif + /** State for various hook callbacks. */ typedef struct JniHookState JniHookState; struct JniHookState{ @@ -476,6 +509,12 @@ static struct { unsigned nInverse; } udf; } metrics; +#if S3JNI_ENABLE_AUTOEXT + struct { + S3JniAutoExtension *pHead; + int isRunning; + } autoExt; +#endif } S3Global; /** @@ -1066,6 +1105,22 @@ static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, return s; } +#if 0 +/** + An alternative form which searches for the PerDbStateJni instance for + pDb with no JNIEnv-specific info. This can be (but _should_ it be?) + called from the context of a separate JNIEnv than the one mapped + to in the returned object. Returns 0 if no match is found. +*/ +FIXME_THREADING +static PerDbStateJni * PerDbStateJni_for_db2(sqlite3 *pDb){ + PerDbStateJni * s = S3Global.perDb.aUsed; + for( ; pDb && s; s = s->pNext){ + if(s->pDb == pDb) return s; + } + return 0; +} +#endif /** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. @@ -1633,6 +1688,50 @@ WRAP_INT_SVALUE(1value_1numeric_1type, sqlite3_value_numeric_type) WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) +#if S3JNI_ENABLE_AUTOEXT +/* auto-extension is very incomplete */ +static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ + return new_NativePointerHolder_object(env, S3ClassNames.sqlite3, sv); +} +/*static*/ int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *pThunk){ + S3JniAutoExtension const * pAX = S3Global.autoExt.pHead; + jobject jDb; + int rc; + JNIEnv * env = 0; + if(S3Global.autoExt.isRunning){ + *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " + "auto-extensions are running."); + return SQLITE_MISUSE; + } + if( S3Global.jvm->GetEnv(S3Global.jvm, (void **)&env, JNI_VERSION_1_8) ){ + *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); + return SQLITE_ERROR; + } + S3Global.autoExt.isRunning = 1; + jDb = new_sqlite3_wrapper( env, pDb ); + EXCEPTION_IS_FATAL("Cannot create sqlite3 wrapper object."); + // Now we need PerDbStateJni_for_db(env, jDb, pDb, 1) + // and rewire sqlite3_open(_v2()) to use OutputPointer.sqlite3 + // so that they can have this same jobject. + for( ; pAX; pAX = pAX->pNext ){ + JNIEnv * const env = pAX->env + /* ^^^ is this correct, or must we use the JavaVM::GetEnv()'s env + instead? */; + rc = (*env)->CallVoidMethod(env, pAX->jObj, pAX->midFunc, jDb); + IFTHREW { + *pzErr = sqlite3_mprintf("auto-extension threw. TODO: extract error message."); + rc = SQLITE_ERROR; + break; + } + } + UNREF_L(jDb); + S3Global.autoExt.isRunning = 0; + return rc; +} +JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){} +#endif /* S3JNI_ENABLE_AUTOEXT */ + JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ int rc; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index cb42dee577..edb75ae04e 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1104,7 +1104,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_00024Int32_2 (JNIEnv *, jclass, jobject, jint, jobject); /* diff --git a/ext/jni/src/org/sqlite/jni/Authorizer.java b/ext/jni/src/org/sqlite/jni/Authorizer.java index 3fd6861fec..114c27fc63 100644 --- a/ext/jni/src/org/sqlite/jni/Authorizer.java +++ b/ext/jni/src/org/sqlite/jni/Authorizer.java @@ -14,7 +14,7 @@ package org.sqlite.jni; /** - A wrapper for use with sqlite3_set_authorizer(). + A callback for use with sqlite3_set_authorizer(). */ public interface Authorizer { /** diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 15077acdb4..758ea717f0 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -70,12 +70,12 @@ public final class Fts5ExtensionApi extends NativePointerHolder Date: Sun, 6 Aug 2023 21:29:13 +0000 Subject: [PATCH 066/148] Completely rework how the JNI sqlite3_open(_v2) and sqlite3_prepare(_vN)() bindings deal with output pointers to give the JNI side full control over the origin of db and stmt handles (necessary for solving chicken/egg situations in auto-extensions and prepare-time trace). Lots of adjacent internal API renaming. FossilOrigin-Name: 644999caff9db79562d45520d94aaa24ee88c65e397b6fb9c20a4f0e7f84e1a5 --- ext/jni/src/c/sqlite3-jni.c | 188 ++++++++++------- ext/jni/src/c/sqlite3-jni.h | 12 +- ext/jni/src/org/sqlite/jni/OutputPointer.java | 14 ++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 29 +-- ext/jni/src/org/sqlite/jni/Tester1.java | 192 +++++++++--------- ext/jni/src/org/sqlite/jni/sqlite3.java | 2 + ext/jni/src/org/sqlite/jni/sqlite3_value.java | 2 + manifest | 24 +-- manifest.uuid | 2 +- 9 files changed, 264 insertions(+), 201 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 6a490404f7..87f530d8a0 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -189,10 +189,10 @@ /** Helpers for extracting pointers from jobjects, noting that the corresponding Java interfaces have already done the type-checking. */ -#define PtrGet_sqlite3(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3) -#define PtrGet_sqlite3_stmt(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_stmt) -#define PtrGet_sqlite3_value(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_value) -#define PtrGet_sqlite3_context(OBJ) getNativePointer(env,OBJ,S3ClassNames.sqlite3_context) +#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3_context) /* Helpers for Java value reference management. */ static inline jobject new_global_ref(JNIEnv *env, jobject v){ return v ? (*env)->NewGlobalRef(env, v) : NULL; @@ -215,6 +215,8 @@ static const struct { const char * const OutputPointer_Int64; const char * const OutputPointer_String; const char * const OutputPointer_ByteArray; + const char * const OutputPointer_sqlite3; + const char * const OutputPointer_sqlite3_stmt; #ifdef SQLITE_ENABLE_FTS5 const char * const Fts5Context; const char * const Fts5ExtensionApi; @@ -231,6 +233,8 @@ static const struct { "org/sqlite/jni/OutputPointer$Int64", "org/sqlite/jni/OutputPointer$String", "org/sqlite/jni/OutputPointer$ByteArray", + "org/sqlite/jni/OutputPointer$sqlite3", + "org/sqlite/jni/OutputPointer$sqlite3_stmt", #ifdef SQLITE_ENABLE_FTS5 "org/sqlite/jni/Fts5Context", "org/sqlite/jni/Fts5ExtensionApi", @@ -296,7 +300,7 @@ static const struct { #define JSTR_TOC(ARG) (*env)->GetStringUTFChars(env, ARG, NULL) #define JSTR_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseStringUTFChars(env, ARG, VAR) #define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) -#define JBA_RELEASE(ARG,VAR) if(ARG) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) +#define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) /* Marker for code which needs(?) to be made thread-safe. */ #define FIXME_THREADING @@ -948,7 +952,11 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char * return freeSlot; } -static jfieldID getNativePointerField(JNIEnv * const env, jclass klazz){ +/** + Returns the ID of the "nativePointer" field from the given + NativePointerHolder class. + */ +static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ jfieldID rv = (*env)->GetFieldID(env, klazz, "nativePointer", "J"); EXCEPTION_IS_FATAL("Code maintenance required: missing nativePointer field."); return rv; @@ -959,7 +967,7 @@ static jfieldID getNativePointerField(JNIEnv * const env, jclass klazz){ zClassName must be a static string so we can use its address as a cache key. */ -static void setNativePointer(JNIEnv * env, jobject ppOut, const void * p, +static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, const char *zClassName){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); @@ -970,7 +978,7 @@ static void setNativePointer(JNIEnv * env, jobject ppOut, const void * p, }else{ jclass const klazz = cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, ppOut); - setter = getNativePointerField(env, klazz); + setter = NativePointerHolder_getField(env, klazz); if(cacheLine){ assert(cacheLine->klazz); assert(!cacheLine->fidValue); @@ -987,9 +995,8 @@ static void setNativePointer(JNIEnv * env, jobject ppOut, const void * p, zClassName must be a static string so we can use its address as a cache key. */ -static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassName){ - if( 0==pObj ) return 0; - else{ +static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zClassName){ + if( pObj ){ jfieldID getter = 0; void * rv = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); @@ -998,7 +1005,7 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam }else{ jclass const klazz = cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, pObj); - getter = getNativePointerField(env, klazz); + getter = NativePointerHolder_getField(env, klazz); if(cacheLine){ assert(cacheLine->klazz); assert(zClassName == cacheLine->zClassName); @@ -1008,6 +1015,8 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam rv = (void*)(*env)->GetLongField(env, pObj, getter); IFTHREW_REPORT; return rv; + }else{ + return 0; } } @@ -1015,7 +1024,7 @@ static void * getNativePointer(JNIEnv * env, jobject pObj, const char *zClassNam Extracts the new PerDbStateJni instance from the free-list, or allocates one if needed, associats it with pDb, and returns. Returns NULL on OOM. pDb MUST be associated with jDb via - setNativePointer(). + NativePointerHolder_set(). */ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ PerDbStateJni * rv; @@ -1179,7 +1188,7 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, } /** - Common init for setOutputInt32() and friends. zClassName must be a + Common init for OutputPointer_set_Int32() and friends. zClassName must be a pointer from S3ClassNames. jOut must be an instance of that class. Fetches the jfieldID for jOut's [value] property, which must be of the type represented by the JNI type signature zTypeSig, and @@ -1213,17 +1222,35 @@ static void setupOutputPointer(JNIEnv * const env, const char *zClassName, /* Sets the value property of the OutputPointer.Int32 jOut object to v. */ -static void setOutputInt32(JNIEnv * const env, jobject const jOut, int v){ +static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_Int32, "I", jOut, &setter); (*env)->SetIntField(env, jOut, setter, (jint)v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } +static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, + jobject jDb){ + jfieldID setter = 0; + setupOutputPointer(env, S3ClassNames.OutputPointer_sqlite3, + "Lorg/sqlite/jni/sqlite3;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, jDb); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); +} + +static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, + jobject jStmt){ + jfieldID setter = 0; + setupOutputPointer(env, S3ClassNames.OutputPointer_sqlite3_stmt, + "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); + (*env)->SetObjectField(env, jOut, setter, jStmt); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); +} + #ifdef SQLITE_ENABLE_FTS5 /* Sets the value property of the OutputPointer.Int64 jOut object to v. */ -static void setOutputInt64(JNIEnv * const env, jobject const jOut, jlong v){ +static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_Int64, "J", jOut, &setter); (*env)->SetLongField(env, jOut, setter, v); @@ -1232,7 +1259,7 @@ static void setOutputInt64(JNIEnv * const env, jobject const jOut, jlong v){ #if 0 /* Sets the value property of the OutputPointer.ByteArray jOut object to v. */ -static void setOutputByteArray(JNIEnv * const env, jobject const jOut, +static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, jbyteArray const v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_ByteArray, "[B", @@ -1243,7 +1270,7 @@ static void setOutputByteArray(JNIEnv * const env, jobject const jOut, #endif /* Sets the value property of the OutputPointer.String jOut object to v. */ -static void setOutputString(JNIEnv * const env, jobject const jOut, +static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, jstring const v){ jfieldID setter = 0; setupOutputPointer(env, S3ClassNames.OutputPointer_String, @@ -1367,7 +1394,7 @@ static void ResultJavaVal_finalizer(void *v){ /** Returns a new Java instance of the class named by zClassName, which MUST be interface-compatible with NativePointerHolder and MUST have - a no-arg constructor. Its setNativePointer() method is passed + a no-arg constructor. Its NativePointerHolder_set() method is passed pNative. Hypothetically returns NULL if Java fails to allocate, but the JNI docs are not entirely clear on that detail. @@ -1402,21 +1429,22 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zC assert(ctor); rv = (*env)->NewObject(env, klazz, ctor); EXCEPTION_IS_FATAL("No-arg constructor threw."); - if(rv) setNativePointer(env, rv, pNative, zClassName); + if(rv) NativePointerHolder_set(env, rv, pNative, zClassName); return rv; } -static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_value, sv); +static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ + return new_NativePointerHolder_object(env, S3ClassNames.sqlite3, sv); } - static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_context, sv); } - static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_stmt, sv); } +static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ + return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_value, sv); +} enum UDFType { UDF_SCALAR = 1, @@ -1690,9 +1718,6 @@ WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) #if S3JNI_ENABLE_AUTOEXT /* auto-extension is very incomplete */ -static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.sqlite3, sv); -} /*static*/ int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *pThunk){ S3JniAutoExtension const * pAX = S3Global.autoExt.pHead; @@ -1872,7 +1897,7 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); if(ps) PerDbStateJni_set_aside(ps) /* MUST come after close() because of ps->trace. */; - setNativePointer(env, jDb, 0, S3ClassNames.sqlite3); + NativePointerHolder_set(env, jDb, 0, S3ClassNames.sqlite3); return (jint)rc; } @@ -2177,7 +2202,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_ int pOut = 0; rc = sqlite3_db_config( ps->pDb, (int)op, &pOut ); if( 0==rc && jOut ){ - setOutputInt32(env, jOut, pOut); + OutputPointer_set_Int32(env, jOut, pOut); } break; } @@ -2346,7 +2371,7 @@ JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ JNIEnvCache * const jc = S3Global_env_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_finalize(pStmt); - setNativePointer(env, jpStmt, 0, S3ClassNames.sqlite3_stmt); + NativePointerHolder_set(env, jpStmt, 0, S3ClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pPrev); } return rc; @@ -2367,16 +2392,19 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ is returned, else theRc is returned. In in case, *ppDb is stored in jDb's native pointer property (even if it's NULL). */ -static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jDb, int theRc){ +static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jOut, int theRc){ + jobject jDb = 0; if(*ppDb){ - PerDbStateJni * const s = PerDbStateJni_for_db(env, jDb, *ppDb, 1); + jDb = new_sqlite3_wrapper(env, *ppDb); + PerDbStateJni * const s = jDb ? PerDbStateJni_for_db(env, jDb, *ppDb, 1) : 0; if(!s && 0==theRc){ + UNREF_L(jDb); sqlite3_close(*ppDb); *ppDb = 0; theRc = SQLITE_NOMEM; } } - setNativePointer(env, jDb, *ppDb, S3ClassNames.sqlite3); + OutputPointer_set_sqlite3(env, jOut, jDb); return theRc; } @@ -2408,53 +2436,72 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, /* Proxy for the sqlite3_prepare[_v2/3]() family. */ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass self, - jobject jpDb, jbyteArray baSql, + jobject jDb, jbyteArray baSql, jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ sqlite3_stmt * pStmt = 0; + jobject jStmt = 0; const char * zTail = 0; jbyte * const pBuf = JBA_TOC(baSql); JNIEnvCache * const jc = S3Global_env_cache(env); - jobject const pOldStmt = stmt_set_current(jc, jOutStmt); + jobject const pOldStmt = stmt_set_current(jc, 0); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); + if( !pBuf ){ + rc = baSql ? SQLITE_MISUSE : SQLITE_NOMEM; + goto end; + } + jStmt = new_sqlite3_stmt_wrapper(env, 0); + if( !jStmt ){ + rc = SQLITE_NOMEM; + goto end; + } switch( prepVersion ){ - case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jpDb), (const char *)pBuf, + case 1: rc = sqlite3_prepare(PtrGet_sqlite3(jDb), (const char *)pBuf, (int)nMax, &pStmt, &zTail); break; - case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jpDb), (const char *)pBuf, + case 2: rc = sqlite3_prepare_v2(PtrGet_sqlite3(jDb), (const char *)pBuf, (int)nMax, &pStmt, &zTail); break; - case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jpDb), (const char *)pBuf, + case 3: rc = sqlite3_prepare_v3(PtrGet_sqlite3(jDb), (const char *)pBuf, (int)nMax, (unsigned int)prepFlags, &pStmt, &zTail); break; default: assert(0 && "Invalid prepare() version"); } +end: JBA_RELEASE(baSql,pBuf); - if( 0!=outTail ){ - assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); - assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); - setOutputInt32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); + if( 0==rc ){ + if( 0!=outTail ){ + /* Noting that pBuf is deallocated now but its address is all we need. */ + assert(zTail ? ((void*)zTail>=(void*)pBuf) : 1); + assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); + OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); + } + NativePointerHolder_set(env, jStmt, pStmt, S3ClassNames.sqlite3_stmt); + }else{ + UNREF_L(jStmt); + jStmt = 0; } - setNativePointer(env, jOutStmt, pStmt, S3ClassNames.sqlite3_stmt); + OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); + //NativePointerHolder_set(env, jOutStmt, pStmt, S3ClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } -JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, +JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, jint nMax, jobject jOutStmt, jobject outTail){ - return sqlite3_jni_prepare_v123(1, env, self, jpDb, baSql, nMax, 0, + return sqlite3_jni_prepare_v123(1, env, self, jDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, +JDECL(jint,1prepare_1v2)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, jint nMax, jobject jOutStmt, jobject outTail){ - return sqlite3_jni_prepare_v123(2, env, self, jpDb, baSql, nMax, 0, + return sqlite3_jni_prepare_v123(2, env, self, jDb, baSql, nMax, 0, jOutStmt, outTail); } -JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jpDb, jbyteArray baSql, +JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, jint nMax, jint prepFlags, jobject jOutStmt, jobject outTail){ - return sqlite3_jni_prepare_v123(3, env, self, jpDb, baSql, nMax, + return sqlite3_jni_prepare_v123(3, env, self, jDb, baSql, nMax, prepFlags, jOutStmt, outTail); } @@ -2797,6 +2844,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ case SQLITE_TRACE_STMT: jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); if(!jX) return SQLITE_NOMEM; + /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ jP = jc->currentStmt; break; case SQLITE_TRACE_PROFILE: @@ -2806,15 +2854,13 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); jP = jc->currentStmt; if(!jP){ + // This will be the case during prepare() b/c we don't have the + // pointer in time to wrap it before tracing is triggered. jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); if(!jP){ UNREF_L(jX); return SQLITE_NOMEM; } - MARKER(("WARNING: created new sqlite3_stmt wrapper for TRACE_PROFILE. stmt@%p\n" - "This means we have missed a route into the tracing API and it " - "needs the stmt_set_current() treatment which is littered around " - "a handful of other functions in this file.\n", pP)); } break; case SQLITE_TRACE_ROW: @@ -3092,10 +3138,10 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ JNIEXPORT ReturnType JNICALL \ JFuncNameFtsTok(Suffix) -#define PtrGet_fts5_api(OBJ) getNativePointer(env,OBJ,S3ClassNames.fts5_api) -#define PtrGet_fts5_tokenizer(OBJ) getNativePointer(env,OBJ,S3ClassNames.fts5_tokenizer) -#define PtrGet_Fts5Context(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Context) -#define PtrGet_Fts5Tokenizer(OBJ) getNativePointer(env,OBJ,S3ClassNames.Fts5Tokenizer) +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.fts5_tokenizer) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.Fts5Tokenizer) #define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() /** @@ -3226,7 +3272,7 @@ JDECLFtsXA(jint,xColumnSize)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOut32) Fts5ExtDecl; int n1 = 0; int const rc = fext->xColumnSize(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1); - if( 0==rc ) setOutputInt32(env, jOut32, n1); + if( 0==rc ) OutputPointer_set_Int32(env, jOut32, n1); return rc; } @@ -3242,7 +3288,7 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; if( pz ){ if( jstr ){ - setOutputString(env, jOut, jstr); + OutputPointer_set_String(env, jOut, jstr); UNREF_L(jstr)/*jOut has a reference*/; }else{ rc = SQLITE_NOMEM; @@ -3256,7 +3302,7 @@ JDECLFtsXA(jint,xColumnTotalSize)(JENV_OSELF,jobject jCtx, jint iCol, jobject jO Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xColumnTotalSize(PtrGet_Fts5Context(jCtx), (int)iCol, &nOut); - if( 0==rc && jOut64 ) setOutputInt64(env, jOut64, (jlong)nOut); + if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); return (jint)rc; } @@ -3372,9 +3418,9 @@ JDECLFtsXA(jint,xInst)(JENV_OSELF,jobject jCtx, jint iIdx, jobject jOutPhrase, int n1 = 0, n2 = 2, n3 = 0; int const rc = fext->xInst(PtrGet_Fts5Context(jCtx), (int)iIdx, &n1, &n2, &n3); if( 0==rc ){ - setOutputInt32(env, jOutPhrase, n1); - setOutputInt32(env, jOutCol, n2); - setOutputInt32(env, jOutOff, n3); + OutputPointer_set_Int32(env, jOutPhrase, n1); + OutputPointer_set_Int32(env, jOutCol, n2); + OutputPointer_set_Int32(env, jOutOff, n3); } return rc; } @@ -3383,7 +3429,7 @@ JDECLFtsXA(jint,xInstCount)(JENV_OSELF,jobject jCtx, jobject jOut32){ Fts5ExtDecl; int nOut = 0; int const rc = fext->xInstCount(PtrGet_Fts5Context(jCtx), &nOut); - if( 0==rc && jOut32 ) setOutputInt32(env, jOut32, nOut); + if( 0==rc && jOut32 ) OutputPointer_set_Int32(env, jOut32, nOut); return (jint)rc; } @@ -3442,8 +3488,8 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, rc = fext->xPhraseFirst(PtrGet_Fts5Context(jCtx), (int)iPhrase, &iter, &iCol, &iOff); if( 0==rc ){ - setOutputInt32(env, jOutCol, iCol); - setOutputInt32(env, jOutOff, iOff); + OutputPointer_set_Int32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutOff, iOff); s3jni_phraseIter_NToJ(env, jc, &iter, jIter); } return rc; @@ -3459,7 +3505,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, rc = fext->xPhraseFirstColumn(PtrGet_Fts5Context(jCtx), (int)iPhrase, &iter, &iCol); if( 0==rc ){ - setOutputInt32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutCol, iCol); s3jni_phraseIter_NToJ(env, jc, &iter, jIter); } return rc; @@ -3475,8 +3521,8 @@ JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, s3jni_phraseIter_JToN(env, jc, jIter, &iter); fext->xPhraseNext(PtrGet_Fts5Context(jCtx), &iter, &iCol, &iOff); - setOutputInt32(env, jOutCol, iCol); - setOutputInt32(env, jOutOff, iOff); + OutputPointer_set_Int32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutOff, iOff); s3jni_phraseIter_NToJ(env, jc, &iter, jIter); } @@ -3489,7 +3535,7 @@ JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; s3jni_phraseIter_JToN(env, jc, jIter, &iter); fext->xPhraseNextColumn(PtrGet_Fts5Context(jCtx), &iter, &iCol); - setOutputInt32(env, jOutCol, iCol); + OutputPointer_set_Int32(env, jOutCol, iCol); s3jni_phraseIter_NToJ(env, jc, &iter, jIter); } @@ -3562,7 +3608,7 @@ JDECLFtsXA(jint,xRowCount)(JENV_OSELF,jobject jCtx, jobject jOut64){ Fts5ExtDecl; sqlite3_int64 nOut = 0; int const rc = fext->xRowCount(PtrGet_Fts5Context(jCtx), &nOut); - if( 0==rc && jOut64 ) setOutputInt64(env, jOut64, (jlong)nOut); + if( 0==rc && jOut64 ) OutputPointer_set_Int64(env, jOut64, (jlong)nOut); return (jint)rc; } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index edb75ae04e..287df65558 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1104,7 +1104,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_00024Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2 (JNIEnv *, jclass, jobject, jint, jobject); /* @@ -1206,7 +1206,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1libversion_1numbe /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_open - * Signature: (Ljava/lang/String;Lorg/sqlite/jni/sqlite3;)I + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open (JNIEnv *, jclass, jstring, jobject); @@ -1214,7 +1214,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_open_v2 - * Signature: (Ljava/lang/String;Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I + * Signature: (Ljava/lang/String;Lorg/sqlite/jni/OutputPointer/sqlite3;ILjava/lang/String;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2 (JNIEnv *, jclass, jstring, jobject, jint, jstring); @@ -1222,7 +1222,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1open_1v2 /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_prepare - * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); @@ -1230,7 +1230,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_prepare_v2 - * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/sqlite3;[BILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 (JNIEnv *, jclass, jobject, jbyteArray, jint, jobject, jobject); @@ -1238,7 +1238,7 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v2 /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_prepare_v3 - * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/sqlite3;[BIILorg/sqlite/jni/OutputPointer/sqlite3_stmt;Lorg/sqlite/jni/OutputPointer/Int32;)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1prepare_1v3 (JNIEnv *, jclass, jobject, jbyteArray, jint, jint, jobject, jobject); diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index c4fbcb116e..065e133202 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -22,6 +22,20 @@ package org.sqlite.jni; autoboxing at that level. */ public final class OutputPointer { + public static final class sqlite3 { + private org.sqlite.jni.sqlite3 value; + public sqlite3(){value = null;} + public void clear(){value = null;} + public final org.sqlite.jni.sqlite3 getValue(){return value;} + } + + public static final class sqlite3_stmt { + private org.sqlite.jni.sqlite3_stmt value; + public sqlite3_stmt(){value = null;} + public void clear(){value = null;} + public final org.sqlite.jni.sqlite3_stmt getValue(){return value;} + } + public static final class Int32 { private int value; public Int32(){this(0);} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index d8f67cd1f9..68bd4e246a 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -476,10 +476,10 @@ public final class SQLite3Jni { pass to, e.g., the sqlite3_collation_needed() callback. */ public static native int sqlite3_open(@Nullable String filename, - @NotNull sqlite3 ppDb); + @NotNull OutputPointer.sqlite3 ppDb); public static native int sqlite3_open_v2(@Nullable String filename, - @NotNull sqlite3 ppDb, + @NotNull OutputPointer.sqlite3 ppDb, int flags, @Nullable String zVfs); /** @@ -503,24 +503,24 @@ public final class SQLite3Jni { */ private static native int sqlite3_prepare(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, - @NotNull sqlite3_stmt outStmt, + @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset); public static int sqlite3_prepare(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull sqlite3_stmt outStmt, + @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset){ return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); } public static int sqlite3_prepare(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull sqlite3_stmt outStmt){ + @NotNull OutputPointer.sqlite3_stmt outStmt){ return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, null); } public static int sqlite3_prepare(@NotNull sqlite3 db, @NotNull String sql, - @NotNull sqlite3_stmt outStmt){ + @NotNull OutputPointer.sqlite3_stmt outStmt){ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); } @@ -528,24 +528,24 @@ public final class SQLite3Jni { private static native int sqlite3_prepare_v2(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, - @NotNull sqlite3_stmt outStmt, + @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset); public static int sqlite3_prepare_v2(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull sqlite3_stmt outStmt, + @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset){ return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); } public static int sqlite3_prepare_v2(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull sqlite3_stmt outStmt){ + @NotNull OutputPointer.sqlite3_stmt outStmt){ return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, null); } public static int sqlite3_prepare_v2(@NotNull sqlite3 db, @NotNull String sql, - @NotNull sqlite3_stmt outStmt){ + @NotNull OutputPointer.sqlite3_stmt outStmt){ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); } @@ -553,12 +553,12 @@ public final class SQLite3Jni { private static native int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, int prepFlags, - @NotNull sqlite3_stmt outStmt, + @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset); public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, - @NotNull sqlite3_stmt outStmt, + @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset){ return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, pTailOffset); } @@ -566,12 +566,13 @@ public final class SQLite3Jni { public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, - @NotNull sqlite3_stmt outStmt){ + @NotNull OutputPointer.sqlite3_stmt outStmt){ return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, null); } public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull String sql, - int prepFlags, @NotNull sqlite3_stmt outStmt){ + int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt){ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index d25d879173..e96038d4d8 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -22,6 +22,8 @@ public class Tester1 { } static final Metrics metrics = new Metrics(); + private static final OutputPointer.sqlite3_stmt outStmt + = new OutputPointer.sqlite3_stmt(); public static void out(T val){ System.out.print(val); @@ -53,11 +55,11 @@ public class Tester1 { } public static sqlite3 createNewDb(){ - sqlite3 db = new sqlite3(); - affirm(0 == db.getNativePointer()); - int rc = sqlite3_open(":memory:", db); + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; affirm(0 == rc); + sqlite3 db = out.getValue(); affirm(0 != db.getNativePointer()); rc = sqlite3_busy_timeout(db, 2000); affirm( 0 == rc ); @@ -69,52 +71,63 @@ public class Tester1 { } public static int execSql(sqlite3 db, boolean throwOnError, 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(); - int rc = 0; - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, - sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); - if(throwOnError) affirm(0 == rc); - else if( 0!=rc ) break; - pos = oTail.getValue(); - affirm(0 != stmt.getNativePointer()); - while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ - } - sqlite3_finalize(stmt); - affirm(0 == stmt.getNativePointer()); - if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ - if(throwOnError){ - throw new RuntimeException("db op failed with rc="+rc); - }else{ - break; - } - } + OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + if(throwOnError) affirm(0 == rc); + else if( 0!=rc ) break; + stmt = outStmt.getValue(); + pos = oTail.getValue(); + affirm(0 != stmt.getNativePointer()); + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ } sqlite3_finalize(stmt); - if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; - return rc; + affirm(0 == stmt.getNativePointer()); + if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ + if(throwOnError){ + throw new RuntimeException("db op failed with rc="+rc); + }else{ + break; + } + } + } + sqlite3_finalize(stmt); + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + return rc; } public static void execSql(sqlite3 db, String sql){ execSql(db, true, sql); } + public static sqlite3_stmt prepare(sqlite3 db, String sql){ + outStmt.clear(); + int rc = sqlite3_prepare(db, sql, outStmt); + affirm( 0 == rc ); + final sqlite3_stmt rv = outStmt.getValue(); + outStmt.clear(); + affirm( 0 != rv.getNativePointer() ); + return rv; + } + private static void testOpenDb1(){ - sqlite3 db = new sqlite3(); - affirm(0 == db.getNativePointer()); - int rc = sqlite3_open(":memory:", db); - ++metrics.dbOpen; - affirm(0 == rc); - affirm(0 < db.getNativePointer()); - sqlite3_close_v2(db); + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open(":memory:", out); + ++metrics.dbOpen; + sqlite3 db = out.getValue(); + affirm(0 == rc); + affirm(0 < db.getNativePointer()); + sqlite3_close_v2(db); affirm(0 == db.getNativePointer()); } @@ -130,13 +143,13 @@ public class Tester1 { } private static void testOpenDb2(){ - sqlite3 db = new sqlite3(); - affirm(0 == db.getNativePointer()); - int rc = sqlite3_open_v2(":memory:", db, + final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + int rc = sqlite3_open_v2(":memory:", out, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null); ++metrics.dbOpen; affirm(0 == rc); + sqlite3 db = out.getValue(); affirm(0 < db.getNativePointer()); sqlite3_close_v2(db); affirm(0 == db.getNativePointer()); @@ -145,10 +158,9 @@ public class Tester1 { private static void testPrepare123(){ sqlite3 db = createNewDb(); int rc; - sqlite3_stmt stmt = new sqlite3_stmt(); - affirm(0 == stmt.getNativePointer()); - rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", stmt); + rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); affirm(0 == rc); + sqlite3_stmt stmt = outStmt.getValue(); affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); affirm(SQLITE_DONE == rc); @@ -169,8 +181,9 @@ public class Tester1 { } //outln("SQL chunk #"+n+" length = "+sqlChunk.length+", pos = "+pos); if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, stmt, oTail); + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); affirm(0 == rc); + stmt = outStmt.getValue(); pos = oTail.getValue(); /*outln("SQL tail pos = "+pos+". Chunk = "+ (new String(Arrays.copyOfRange(sqlChunk,0,pos), @@ -192,8 +205,9 @@ public class Tester1 { rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", - SQLITE_PREPARE_NORMALIZE, stmt); + SQLITE_PREPARE_NORMALIZE, outStmt); affirm(0 == rc); + stmt = outStmt.getValue(); affirm(0 != stmt.getNativePointer()); sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer() ); @@ -204,9 +218,7 @@ public class Tester1 { 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); - affirm(0 == rc); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(:a);"); affirm(1 == sqlite3_bind_parameter_count(stmt)); final int paramNdx = sqlite3_bind_parameter_index(stmt, ":a"); affirm(1 == paramNdx); @@ -216,6 +228,7 @@ public class Tester1 { int changesT = sqlite3_total_changes(db); long changes64 = sqlite3_changes64(db); long changesT64 = sqlite3_total_changes64(db); + int rc; for(int i = 99; i < 102; ++i ){ total1 += i; rc = sqlite3_bind_int(stmt, paramNdx, i); @@ -233,8 +246,7 @@ public class Tester1 { affirm(sqlite3_total_changes(db) > changesT); affirm(sqlite3_changes64(db) > changes64); affirm(sqlite3_total_changes64(db) > changesT64); - rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - affirm(0 == rc); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); int total2 = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ total2 += sqlite3_column_int(stmt, 0); @@ -252,8 +264,7 @@ public class Tester1 { 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); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); long total1 = 0; for(long i = 0xffffffff; i < 0xffffffff + 3; ++i ){ total1 += i; @@ -262,8 +273,7 @@ public class Tester1 { sqlite3_reset(stmt); } sqlite3_finalize(stmt); - rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - affirm(0 == rc); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); long total2 = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ total2 += sqlite3_column_int64(stmt, 0); @@ -276,8 +286,7 @@ public class Tester1 { 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); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); double total1 = 0; for(double i = 1.5; i < 5.0; i = i + 1.0 ){ total1 += i; @@ -286,8 +295,7 @@ public class Tester1 { sqlite3_reset(stmt); } sqlite3_finalize(stmt); - rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - affirm(0 == rc); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); double total2 = 0; int counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ @@ -303,9 +311,9 @@ public class Tester1 { 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); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); String[] list1 = { "hell🤩", "w😃rld", "!" }; + int rc; for( String e : list1 ){ rc = sqlite3_bind_text(stmt, 1, e); affirm(0 == rc); @@ -314,8 +322,7 @@ public class Tester1 { sqlite3_reset(stmt); } sqlite3_finalize(stmt); - rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - affirm(0 == rc); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); StringBuilder sbuf = new StringBuilder(); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ @@ -333,15 +340,14 @@ public class Tester1 { 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); + sqlite3_stmt stmt = prepare(db, "INSERT INTO t(a) VALUES(?);"); byte[] list1 = { 0x32, 0x33, 0x34 }; - rc = sqlite3_bind_blob(stmt, 1, list1); + int rc = sqlite3_bind_blob(stmt, 1, list1); + affirm( 0==rc ); rc = sqlite3_step(stmt); affirm(SQLITE_DONE == rc); sqlite3_finalize(stmt); - rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a DESC;", stmt); - affirm(0 == rc); + stmt = prepare(db, "SELECT a FROM t ORDER BY a DESC;"); int n = 0; int total = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ @@ -398,9 +404,7 @@ public class Tester1 { affirm( 0 == rc ); rc = sqlite3_collation_needed(db, collLoader); affirm( 0 == rc /* Installing the same object again is a no-op */); - sqlite3_stmt stmt = new sqlite3_stmt(); - rc = sqlite3_prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi", stmt); - affirm( 0 == rc ); + sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi"); int counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ final String val = sqlite3_column_text(stmt, 0); @@ -414,7 +418,7 @@ public class Tester1 { } affirm(3 == counter); sqlite3_finalize(stmt); - sqlite3_prepare(db, "SELECT a FROM t ORDER BY a", stmt); + stmt = prepare(db, "SELECT a FROM t ORDER BY a"); counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ final String val = sqlite3_column_text(stmt, 0); @@ -479,9 +483,7 @@ public class Tester1 { int rc = sqlite3_create_function(db, "myfunc", -1, SQLITE_UTF8, func); affirm(0 == rc); affirm(0 == xFuncAccum.value); - final sqlite3_stmt stmt = new sqlite3_stmt(); - rc = sqlite3_prepare(db, "SELECT myfunc(1,2,3)", stmt); - affirm( 0==rc ); + final sqlite3_stmt stmt = prepare(db, "SELECT myfunc(1,2,3)"); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ affirm( 6 == sqlite3_column_int(stmt, 0) ); @@ -498,15 +500,14 @@ public class Tester1 { private static void testUdfJavaObject(){ final sqlite3 db = createNewDb(); final ValueHolder testResult = new ValueHolder<>(db); - SQLFunction func = new SQLFunction.Scalar(){ + final 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); affirm(0 == rc); - sqlite3_stmt stmt = new sqlite3_stmt(); - sqlite3_prepare(db, "select myfunc()", stmt); + final sqlite3_stmt stmt = prepare(db, "select myfunc()"); affirm( 0 != stmt.getNativePointer() ); affirm( testResult.value == db ); int n = 0; @@ -550,9 +551,7 @@ public class Tester1 { 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); affirm(0 == rc); - sqlite3_stmt stmt = new sqlite3_stmt(); - sqlite3_prepare(db, "select myfunc(a), myfunc(a+10) from t", stmt); - affirm( 0 != stmt.getNativePointer() ); + sqlite3_stmt stmt = prepare(db, "select myfunc(a), myfunc(a+10) from t"); int n = 0; if( SQLITE_ROW == sqlite3_step(stmt) ){ final int v = sqlite3_column_int(stmt, 0); @@ -571,9 +570,7 @@ public class Tester1 { sqlite3_finalize(stmt); affirm( 1==n ); - rc = sqlite3_prepare(db, "select myfunc(a), myfunc(a+a) from t order by a", - stmt); - affirm( 0 == rc ); + stmt = prepare(db, "select myfunc(a), myfunc(a+a) from t order by a"); n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ final int c0 = sqlite3_column_int(stmt, 0); @@ -624,12 +621,11 @@ public class Tester1 { "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, + final sqlite3_stmt stmt = 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); + "FROM twin ORDER BY x;"); affirm( 0 == rc ); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ @@ -693,11 +689,12 @@ public class Tester1 { new Tracer(){ public int xCallback(int traceFlag, Object pNative, Object x){ ++counter.value; + //outln("TRACE "+traceFlag+" pNative = "+pNative.getClass().getName()); switch(traceFlag){ case SQLITE_TRACE_STMT: affirm(pNative instanceof sqlite3_stmt); - affirm(x instanceof String); //outln("TRACE_STMT sql = "+x); + affirm(x instanceof String); affirm( ((String)x).indexOf(nonBmpChar) > 0 ); break; case SQLITE_TRACE_PROFILE: @@ -730,16 +727,18 @@ public class Tester1 { private static void testBusy(){ final String dbName = "_busy-handler.db"; - final sqlite3 db1 = new sqlite3(); - final sqlite3 db2 = new sqlite3(); + final OutputPointer.sqlite3 outDb = new OutputPointer.sqlite3(); - int rc = sqlite3_open(dbName, db1); + int rc = sqlite3_open(dbName, outDb); ++metrics.dbOpen; affirm( 0 == rc ); + final sqlite3 db1 = outDb.getValue(); execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); - rc = sqlite3_open(dbName, db2); + rc = sqlite3_open(dbName, outDb); ++metrics.dbOpen; affirm( 0 == rc ); + affirm( outDb.getValue() != db1 ); + final sqlite3 db2 = outDb.getValue(); rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); @@ -760,11 +759,10 @@ public class Tester1 { // Force a locked condition... execSql(db1, "BEGIN EXCLUSIVE"); affirm(!xDestroyed.value); - sqlite3_stmt stmt = new sqlite3_stmt(); - rc = sqlite3_prepare(db2, "SELECT * from t", stmt); + rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); affirm( SQLITE_BUSY == rc); + assert( null == outStmt.getValue() ); affirm( 3 == xBusyCalled.value ); - sqlite3_finalize(stmt); sqlite3_close_v2(db1); affirm(!xDestroyed.value); sqlite3_close_v2(db2); diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java index 99d06af829..8f4ecb772b 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -20,4 +20,6 @@ package org.sqlite.jni; and C via JNI. */ public final class sqlite3 extends NativePointerHolder { + // Only invoked from JNI + private sqlite3(){} } diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_value.java b/ext/jni/src/org/sqlite/jni/sqlite3_value.java index 8aa8c77bbd..2cfb32ff1a 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_value.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_value.java @@ -14,4 +14,6 @@ package org.sqlite.jni; public final class sqlite3_value extends NativePointerHolder { + //! Invoked only from JNI. + private sqlite3_value(){} } diff --git a/manifest b/manifest index dcdf8f77f9..e68cb542c1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Start\sadding\sJNI-side\ssupport\sfor\sauto\sextensions,\sbut\shit\sa\sbrick\swall\swhich\srequires\sslightly\sawkward\ssemantics\schanges\sin\sthe\sJNI\sbindings\sfor\ssqlite3_open(_v2)()\sto\sresolve,\sso\sstash\sthis\s#if'd\sout\sfor\sthe\stime\sbeing. -D 2023-08-06T20:01:30.439 +C Completely\srework\show\sthe\sJNI\ssqlite3_open(_v2)\sand\ssqlite3_prepare(_vN)()\sbindings\sdeal\swith\soutput\spointers\sto\sgive\sthe\sJNI\sside\sfull\scontrol\sover\sthe\sorigin\sof\sdb\sand\sstmt\shandles\s(necessary\sfor\ssolving\schicken/egg\ssituations\sin\sauto-extensions\sand\sprepare-time\strace).\sLots\sof\sadjacent\sinternal\sAPI\srenaming. +D 2023-08-06T21:29:13.410 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c e335541e3eac0e337cc22f0b78a040e73c477d41128845ffe4ba8029fc077994 -F ext/jni/src/c/sqlite3-jni.h 2ef601ab7cef00047ef0907e873f8f7bc4bfa6ee510b0435e070eb8ee7b6c6f0 +F ext/jni/src/c/sqlite3-jni.c 22f463b0bf4e79ccbc0dcd157e8c419e1a6ab1a88afbd565db818aec2802241e +F ext/jni/src/c/sqlite3-jni.h 2108bb9434fe873e08d6388bce102a474b5c6b00c2ea47d8aee257aca2de2a67 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 @@ -246,12 +246,12 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java fcece068415b804aa7843534addb39059ea2b923a9f5dbe91f4f78f066c77991 +F ext/jni/src/org/sqlite/jni/OutputPointer.java 053ea7dbc1234dd70b8948009a52a3f1090403a6fe2ab7b7885b6f08ed26deea F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2ee2d3522ab6bec9337653233f5fb50619120cc5b12ce5deb59035ca2502cdcd -F ext/jni/src/org/sqlite/jni/Tester1.java 04c43f3ec93b362fc1c66430a3125067285259cd4fa5afccdb2fa66b691db8d0 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3edf79fb7a3cb5eaeeaaa074f51521d6d9a962b0dd1ca88074be4fd075258fd8 +F ext/jni/src/org/sqlite/jni/Tester1.java 7ea111e9d52042889f2360e7addfefed4174c1a17dfe6efccf64aaa9a65749cf F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -259,10 +259,10 @@ F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71 F ext/jni/src/org/sqlite/jni/fts5_api.java 8c6b32455d7f85ee3f7f3e71c148bb3c2106f1d5484017daddfd560dd69d4f66 F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b -F ext/jni/src/org/sqlite/jni/sqlite3.java 600c3ddc1ac28ee8f58669fb435fd0d21f2972c652039361fde907d4fe44eb58 +F ext/jni/src/org/sqlite/jni/sqlite3.java ff3729426704626a6019d97bfee512a83f253cf43ffeffbd45b238718154df36 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 72a0698aeb50a183ad146cd29ee04952abb8c36021f6122656aa5ec20469f6f7 -F ext/jni/src/org/sqlite/jni/sqlite3_value.java fd045a09458e0a1b9328b085015b5ca5cc9024e7f91ee299f95da9dfd9a865a7 +F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5f56b007704f2aad4cbc6f0ccd1e1f1c974865971f99451352714ee7e077c284 -R 55ff516d4ad7789048494bf17442d9b9 +P 77a32d238e80fe1d237768d88780043a7bd2b3543e6672536254782cbea0039c +R 218b9f82589d0175238ee5cd53ac1842 U stephan -Z 7da38e0ddc75e18237df446c4596f88f +Z f6066ff3c416a47c98afc4ffb5e0f6a5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b8a927308b..101fba0f15 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -77a32d238e80fe1d237768d88780043a7bd2b3543e6672536254782cbea0039c \ No newline at end of file +644999caff9db79562d45520d94aaa24ee88c65e397b6fb9c20a4f0e7f84e1a5 \ No newline at end of file From 60745265e14204de4475739f205ccc71f4f93ca0 Mon Sep 17 00:00:00 2001 From: stephan Date: Sun, 6 Aug 2023 22:09:09 +0000 Subject: [PATCH 067/148] Rework the sqlite3_open(_v2)() order of operations so that pending auto-extension support can get ahold of the open-time Java state despite the Java/C (sqlite3*) binding not having yet been established. FossilOrigin-Name: 34da294ab558880e81eebd7d261bc590551d5a7d2855e844695cef6394647ea7 --- ext/jni/src/c/sqlite3-jni.c | 112 +++++++++++++++++++++++------------- manifest | 12 ++-- manifest.uuid | 2 +- 3 files changed, 80 insertions(+), 46 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 87f530d8a0..8d88f9f6be 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -968,7 +968,7 @@ static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ as a cache key. */ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, - const char *zClassName){ + const char *zClassName){ jfieldID setter = 0; struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ @@ -1026,9 +1026,9 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC Returns NULL on OOM. pDb MUST be associated with jDb via NativePointerHolder_set(). */ -static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, jobject jDb){ +static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, + jobject jDb){ PerDbStateJni * rv; - assert( pDb ); if(S3Global.perDb.aFree){ rv = S3Global.perDb.aFree; //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); @@ -2382,56 +2382,90 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ return (jlong)sqlite3_last_insert_rowid(PtrGet_sqlite3(jpDb)); } -/** - Code common to both the sqlite3_open() and sqlite3_open_v2() - bindings. Allocates the PerDbStateJni for *ppDb if *ppDb is not - NULL. jDb must be the org.sqlite.jni.sqlite3 object which will hold - the db's native pointer. theRc must be the result code of the open - op(). If allocation of the PerDbStateJni object fails then *ppDb is - passed to sqlite3_close(), *ppDb is assigned to 0, and SQLITE_NOMEM - is returned, else theRc is returned. In in case, *ppDb is stored in - jDb's native pointer property (even if it's NULL). -*/ -static int s3jni_open_post(JNIEnv * const env, sqlite3 **ppDb, jobject jOut, int theRc){ - jobject jDb = 0; - if(*ppDb){ - jDb = new_sqlite3_wrapper(env, *ppDb); - PerDbStateJni * const s = jDb ? PerDbStateJni_for_db(env, jDb, *ppDb, 1) : 0; - if(!s && 0==theRc){ - UNREF_L(jDb); - sqlite3_close(*ppDb); - *ppDb = 0; - theRc = SQLITE_NOMEM; - } +//! Pre-open() code common to sqlite3_open(_v2)(). +static int s3jni_open_pre(JNIEnv * const env, JNIEnvCache **jc, + jstring jDbName, char **zDbName, + PerDbStateJni ** ps, jobject *jDb){ + *jc = S3Global_env_cache(env); + if(!*jc) return SQLITE_NOMEM; + *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; + if(jDbName && !*zDbName) return SQLITE_NOMEM; + *jDb = new_sqlite3_wrapper(env, 0); + if( !*jDb ){ + sqlite3_free(*zDbName); + *zDbName = 0; + return SQLITE_NOMEM; } - OutputPointer_set_sqlite3(env, jOut, jDb); + *ps = PerDbStateJni_alloc(env, 0, *jDb); + return *ps ? 0 : SQLITE_NOMEM; +} + +/** + Post-open() code common to both the sqlite3_open() and + sqlite3_open_v2() bindings. ps->jDb must be the + org.sqlite.jni.sqlite3 object which will hold the db's native + pointer. theRc must be the result code of the open() op. If + *ppDb is NULL then ps is set aside and its state cleared, + else ps is associated with *ppDb. If *ppDb is not NULL then + ps->jDb is stored in jOut (an OutputPointer.sqlite3 instance). + + Returns theRc. +*/ +static int s3jni_open_post(JNIEnv * const env, PerDbStateJni * ps, + sqlite3 **ppDb, jobject jOut, int theRc){ + if(*ppDb){ + assert(ps->jDb); + ps->pDb = *ppDb; + NativePointerHolder_set(env, ps->jDb, *ppDb, S3ClassNames.sqlite3); + }else{ + PerDbStateJni_set_aside(ps); + ps = 0; + } + OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); return theRc; } JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ sqlite3 * pOut = 0; - const char *zName = strName ? JSTR_TOC(strName) : 0; - int nrc = sqlite3_open(zName, &pOut); + char *zName = 0; + jobject jDb = 0; + PerDbStateJni * ps = 0; + JNIEnvCache * jc = 0; + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + if( rc ) return rc; + rc = sqlite3_open(zName, &pOut); //MARKER(("env=%p, *env=%p\n", env, *env)); - nrc = s3jni_open_post(env, &pOut, jOut, nrc); - assert(nrc==0 ? pOut!=0 : 1); - JSTR_RELEASE(strName, zName); - return (jint)nrc; + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + return (jint)rc; } JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, jobject jOut, jint flags, jstring strVfs){ sqlite3 * pOut = 0; - const char *zName = strName ? JSTR_TOC(strName) : 0; - const char *zVfs = strVfs ? JSTR_TOC(strVfs) : 0; - int nrc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + char *zName = 0; + jobject jDb = 0; + PerDbStateJni * ps = 0; + JNIEnvCache * jc = 0; + char *zVfs = 0; + int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); + if( 0==rc && strVfs ){ + zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); + if( !zVfs ){ + rc = SQLITE_NOMEM; + } + } + if( 0==rc ){ + rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); + } /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", zName, zVfs, pOut, (int)flags, nrc));*/ - nrc = s3jni_open_post(env, &pOut, jOut, nrc); - assert(nrc==0 ? pOut!=0 : 1); - JSTR_RELEASE(strName, zName); - JSTR_RELEASE(strVfs, zVfs); - return (jint)nrc; + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + sqlite3_free(zVfs); + return (jint)rc; } /* Proxy for the sqlite3_prepare[_v2/3]() family. */ diff --git a/manifest b/manifest index e68cb542c1..bdb88221fa 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Completely\srework\show\sthe\sJNI\ssqlite3_open(_v2)\sand\ssqlite3_prepare(_vN)()\sbindings\sdeal\swith\soutput\spointers\sto\sgive\sthe\sJNI\sside\sfull\scontrol\sover\sthe\sorigin\sof\sdb\sand\sstmt\shandles\s(necessary\sfor\ssolving\schicken/egg\ssituations\sin\sauto-extensions\sand\sprepare-time\strace).\sLots\sof\sadjacent\sinternal\sAPI\srenaming. -D 2023-08-06T21:29:13.410 +C Rework\sthe\ssqlite3_open(_v2)()\sorder\sof\soperations\sso\sthat\spending\sauto-extension\ssupport\scan\sget\sahold\sof\sthe\sopen-time\sJava\sstate\sdespite\sthe\sJava/C\s(sqlite3*)\sbinding\snot\shaving\syet\sbeen\sestablished. +D 2023-08-06T22:09:09.175 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 22f463b0bf4e79ccbc0dcd157e8c419e1a6ab1a88afbd565db818aec2802241e +F ext/jni/src/c/sqlite3-jni.c 21177d7c3492f84aad0666c43ea6d9bc0ebd544f66dcb41219f966b137e86c1b F ext/jni/src/c/sqlite3-jni.h 2108bb9434fe873e08d6388bce102a474b5c6b00c2ea47d8aee257aca2de2a67 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -2082,8 +2082,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 77a32d238e80fe1d237768d88780043a7bd2b3543e6672536254782cbea0039c -R 218b9f82589d0175238ee5cd53ac1842 +P 644999caff9db79562d45520d94aaa24ee88c65e397b6fb9c20a4f0e7f84e1a5 +R c4d749cda5fec61bfb97a76dd1b71afd U stephan -Z f6066ff3c416a47c98afc4ffb5e0f6a5 +Z cb93209a7d6706bee6dfc5cbdd5b5747 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 101fba0f15..92f5779fcb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -644999caff9db79562d45520d94aaa24ee88c65e397b6fb9c20a4f0e7f84e1a5 \ No newline at end of file +34da294ab558880e81eebd7d261bc590551d5a7d2855e844695cef6394647ea7 \ No newline at end of file From 8af781fc4ddaa0c4f795b55c40816c467c52f1df Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 00:06:31 +0000 Subject: [PATCH 068/148] Bind the auto-extension APIs to JNI. FossilOrigin-Name: 746a5fa079ad80b3c59411202ee601e0b5c50e79e5994d5e464fa06d3c276324 --- ext/jni/src/c/sqlite3-jni.c | 219 +++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 24 ++ ext/jni/src/org/sqlite/jni/AutoExtension.java | 29 +++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 46 ++-- ext/jni/src/org/sqlite/jni/Tester1.java | 54 ++++- manifest | 19 +- manifest.uuid | 2 +- 7 files changed, 338 insertions(+), 55 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/AutoExtension.java diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 8d88f9f6be..054b04484f 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -397,7 +397,7 @@ static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ memset(p, 0, sizeof(NphCacheLine)); } -#define S3JNI_ENABLE_AUTOEXT 0 +#define S3JNI_ENABLE_AUTOEXT 1 #if S3JNI_ENABLE_AUTOEXT /* Whether auto extensions are feasible here is currently unknown due @@ -421,7 +421,6 @@ static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ typedef struct S3JniAutoExtension S3JniAutoExtension; typedef void (*S3JniAutoExtension_xEntryPoint)(sqlite3*); struct S3JniAutoExtension { - JNIEnv * env; jobject jObj; jmethodID midFunc; S3JniAutoExtension_xEntryPoint xEntryPoint; @@ -515,8 +514,18 @@ static struct { } metrics; #if S3JNI_ENABLE_AUTOEXT struct { - S3JniAutoExtension *pHead; - int isRunning; + S3JniAutoExtension *pHead /* Head of the auto-extension list */; + PerDbStateJni * psOpening /* handle to the being-opened db. We + need this so that auto extensions + can have a consistent view of the + cross-language db connection and + behave property if they call further + db APIs. */; + int isRunning /* True while auto extensions are + running. This is used to prohibit + manipulation of the auto-extension + list while extensions are + running. */; } autoExt; #endif } S3Global; @@ -738,6 +747,36 @@ static JNIEnvCache * S3Global_env_cache(JNIEnv * const env){ return row; } +/** + Requires jx to be a Throwable. Calls its getMessage() method and + returns its value converted to a UTF-8 string. The caller owns the + returned string and must eventually sqlite3_free() it. Returns 0 + if there is a problem fetching the info or on OOM. +*/ +static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ + JNIEnvCache * const jc = S3Global_env_cache(env); + jmethodID mid; + jstring msg; + char * zMsg; + jclass const klazz = (*env)->GetObjectClass(env, jx); + EXCEPTION_IS_FATAL("Cannot get class of exception object."); + mid = (*env)->GetMethodID(env, klazz, "getMessage", "()Ljava/lang/String;"); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + return 0; + } + msg = (*env)->CallObjectMethod(env, jx, mid); + IFTHREW{ + EXCEPTION_REPORT; + EXCEPTION_CLEAR; + return 0; + } + zMsg = s3jni_jstring_to_utf8(jc, msg, 0); + UNREF_L(msg); + return zMsg; +} + /** Removes any Java references from s and clears its state. If doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's @@ -1131,6 +1170,59 @@ static PerDbStateJni * PerDbStateJni_for_db2(sqlite3 *pDb){ } #endif +#if S3JNI_ENABLE_AUTOEXT +/** + Unlink ax from S3Global.autoExt and free it. +*/ +static void S3JniAutoExtension_free(JNIEnv * const env, + S3JniAutoExtension * const ax){ + if( ax ){ + if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; + if( ax == S3Global.autoExt.pHead ){ + assert( !ax->pNext ); + S3Global.autoExt.pHead = ax->pNext; + }else if( ax->pPrev ){ + ax->pPrev->pNext = ax->pNext; + } + ax->pNext = ax->pPrev = 0; + UNREF_G(ax->jObj); + sqlite3_free(ax); + } +} + +/** + Allocates a new auto extension and plugs it in to S3Global.autoExt. + Returns 0 on OOM or if there is an error collecting the required + state from jAutoExt (which must be an AutoExtension object). +*/ +static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, + jobject const jAutoExt){ + S3JniAutoExtension * const ax = sqlite3_malloc(sizeof(*ax)); + if( ax ){ + jclass klazz; + memset(ax, 0, sizeof(*ax)); + klazz = (*env)->GetObjectClass(env, jAutoExt); + EXCEPTION_IS_FATAL("Cannot get class of jAutoExt."); + if(!klazz){ + S3JniAutoExtension_free(env, ax); + return 0; + } + ax->midFunc = (*env)->GetMethodID(env, klazz, "xEntryPoint", + "(Lorg/sqlite/jni/sqlite3;)I"); + if(!ax->midFunc){ + MARKER(("Error getting xEntryPoint(sqlite3) from object.")); + S3JniAutoExtension_free(env, ax); + return 0; + } + ax->jObj = REF_G(jAutoExt); + ax->pNext = S3Global.autoExt.pHead; + if( ax->pNext ) ax->pNext->pPrev = ax; + S3Global.autoExt.pHead = ax; + } + return ax; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + /** Requires that jCx be a Java-side sqlite3_context wrapper for pCx. This function calls sqlite3_aggregate_context() to allocate a tiny @@ -1683,7 +1775,6 @@ static void udf_xInverse(sqlite3_context* cx, int argc, // except for this macro-generated subset which are kept together here // at the front... //////////////////////////////////////////////////////////////////////// -WRAP_INT_DB(1errcode, sqlite3_errcode) WRAP_INT_DB(1error_1offset, sqlite3_error_offset) WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) @@ -1717,44 +1808,71 @@ WRAP_INT_SVALUE(1value_1subtype, sqlite3_value_subtype) WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) #if S3JNI_ENABLE_AUTOEXT -/* auto-extension is very incomplete */ -/*static*/ int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, - const struct sqlite3_api_routines *pThunk){ +/* Central auto-extension handler. */ +static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ S3JniAutoExtension const * pAX = S3Global.autoExt.pHead; - jobject jDb; int rc; JNIEnv * env = 0; - if(S3Global.autoExt.isRunning){ + PerDbStateJni * const ps = S3Global.autoExt.psOpening; + + S3Global.autoExt.psOpening = 0; + if( !pAX ){ + assert( 0==S3Global.autoExt.isRunning ); + return 0; + }else if( S3Global.autoExt.isRunning ){ *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " "auto-extensions are running."); return SQLITE_MISUSE; - } - if( S3Global.jvm->GetEnv(S3Global.jvm, (void **)&env, JNI_VERSION_1_8) ){ + }else if(!ps){ + MARKER(("Internal error: cannot find PerDbStateJni for auto-extension\n")); + return SQLITE_ERROR; + }else if( (*S3Global.jvm)->GetEnv(S3Global.jvm, (void **)&env, JNI_VERSION_1_8) ){ + assert(!"Cannot get JNIEnv"); *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); return SQLITE_ERROR; } + assert( !ps->pDb /* it's still being opened */ ); + ps->pDb = pDb; + assert( ps->jDb ); + NativePointerHolder_set(env, ps->jDb, pDb, S3ClassNames.sqlite3); S3Global.autoExt.isRunning = 1; - jDb = new_sqlite3_wrapper( env, pDb ); - EXCEPTION_IS_FATAL("Cannot create sqlite3 wrapper object."); - // Now we need PerDbStateJni_for_db(env, jDb, pDb, 1) - // and rewire sqlite3_open(_v2()) to use OutputPointer.sqlite3 - // so that they can have this same jobject. for( ; pAX; pAX = pAX->pNext ){ - JNIEnv * const env = pAX->env - /* ^^^ is this correct, or must we use the JavaVM::GetEnv()'s env - instead? */; - rc = (*env)->CallVoidMethod(env, pAX->jObj, pAX->midFunc, jDb); + rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); IFTHREW { - *pzErr = sqlite3_mprintf("auto-extension threw. TODO: extract error message."); - rc = SQLITE_ERROR; + jthrowable const ex = (*env)->ExceptionOccurred(env); + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + UNREF_L(ex); + *pzErr = sqlite3_mprintf("auto-extension threw: %s", zMsg); + sqlite3_free(zMsg); + rc = rc ? rc : SQLITE_ERROR; + break; + }else if( rc ){ break; } } - UNREF_L(jDb); S3Global.autoExt.isRunning = 0; return rc; } -JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){} + +JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ + static int once = 0; + S3JniAutoExtension * ax; + + if( !jAutoExt ) return SQLITE_MISUSE; + else if( 0==once && ++once ){ + sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); + } + ax = S3Global.autoExt.pHead; + for( ; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + return 0 /* C API treats this as a no-op. */; + } + } + return S3JniAutoExtension_alloc(env, jAutoExt) ? 0 : SQLITE_NOMEM; +} #endif /* S3JNI_ENABLE_AUTOEXT */ JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, @@ -1879,6 +1997,21 @@ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ return SQLITE_MISUSE; } +#if S3JNI_ENABLE_AUTOEXT +JDECL(jboolean,1cancel_1auto_1extension)(JENV_OSELF, jobject jAutoExt){ + S3JniAutoExtension * ax;; + if( S3Global.autoExt.isRunning ) return JNI_FALSE; + for( ax = S3Global.autoExt.pHead; ax; ax = ax->pNext ){ + if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ + S3JniAutoExtension_free(env, ax); + return JNI_TRUE; + } + } + return JNI_FALSE; +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + + /** Wrapper for sqlite3_close(_v2)(). */ @@ -2331,12 +2464,21 @@ JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ return jRv; } +JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE; +} + JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ - return (*env)->NewStringUTF(env, sqlite3_errmsg(PtrGet_sqlite3(jpDb))); + sqlite3 * const pDb = PtrGet_sqlite3(jpDb); + JNIEnvCache * const jc = pDb ? S3Global_env_cache(env) : 0; + return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; } JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ - return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)); + return (*env)->NewStringUTF(env, sqlite3_errstr((int)rcCode)) + /* We know these values to be plain ASCII, so pose no + MUTF-8 incompatibility */; } JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, @@ -2397,6 +2539,12 @@ static int s3jni_open_pre(JNIEnv * const env, JNIEnvCache **jc, return SQLITE_NOMEM; } *ps = PerDbStateJni_alloc(env, 0, *jDb); +#if S3JNI_ENABLE_AUTOEXT + if(*ps){ + assert(!S3Global.autoExt.psOpening); + S3Global.autoExt.psOpening = *ps; + } +#endif return *ps ? 0 : SQLITE_NOMEM; } @@ -2413,8 +2561,15 @@ static int s3jni_open_pre(JNIEnv * const env, JNIEnvCache **jc, */ static int s3jni_open_post(JNIEnv * const env, PerDbStateJni * ps, sqlite3 **ppDb, jobject jOut, int theRc){ +#if S3JNI_ENABLE_AUTOEXT + assert( S3Global.autoExt.pHead ? 0==S3Global.autoExt.psOpening : 1 ); + S3Global.autoExt.psOpening = 0; +#endif if(*ppDb){ assert(ps->jDb); +#if S3JNI_ENABLE_AUTOEXT + assert( S3Global.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); +#endif ps->pDb = *ppDb; NativePointerHolder_set(env, ps->jDb, *ppDb, S3ClassNames.sqlite3); }else{ @@ -2598,6 +2753,16 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ return rc; } +#if S3JNI_ENABLE_AUTOEXT +JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ + if(!S3Global.autoExt.isRunning){ + while( S3Global.autoExt.pHead ){ + S3JniAutoExtension_free(env, S3Global.autoExt.pHead); + } + } +} +#endif /* S3JNI_ENABLE_AUTOEXT */ + /* sqlite3_result_text/blob() and friends. */ static void result_blob_text(int asBlob, int as64, int eTextRep/*only for (asBlob=0)*/, diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 287df65558..01bf381ae0 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -771,6 +771,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv (JNIEnv *, jclass); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_auto_extension + * Signature: (Lorg/sqlite/jni/AutoExtension;)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1auto_1extension + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_bind_blob @@ -867,6 +875,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1handler JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1busy_1timeout (JNIEnv *, jclass, jobject, jint); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_cancel_auto_extension + * Signature: (Lorg/sqlite/jni/AutoExtension;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1cancel_1auto_1extension + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_changes @@ -1259,6 +1275,14 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1progress_1handler JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset (JNIEnv *, jclass, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_reset_auto_extension + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1reset_1auto_1extension + (JNIEnv *, jclass); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_result_double diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java new file mode 100644 index 0000000000..9816a967c2 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -0,0 +1,29 @@ +/* +** 2023-08-05 +** +** 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 callback for use with sqlite3_auto_extension(). +*/ +public interface AutoExtension { + /** + Must function as described for the sqlite3_auto_extension(), + with the caveat that the signature is more limited. + + As an exception to the callbacks-must-not-throw rule, + AutoExtensions may do so and the exception's error + message will be set as the db's error string. + */ + int xEntryPoint(sqlite3 db); +} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 68bd4e246a..303ab96662 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -166,24 +166,24 @@ public final class SQLite3Jni { // grouped by category. - // Auto-extensions cannot currently work properly in our setup - // for reasons explained in sqlite3-jni.c. - // - // /** - // Functions almost as documented for the C API, with these - // exceptions: - // - // - The callback interface is more limited because of - // cross-language differences. - // - // - All of the auto-extension routines will fail without side - // effects if invoked from within the execution of an - // auto-extension. - // - // See the AutoExtension class docs for more information. - // */ - // private static native int sqlite3_auto_extension(@NotNull AutoExtension callback); + /** + Functions almost as documented for the C API, with these + exceptions: + - The callback interface is more limited because of + cross-language differences. + + - All of the auto extension routines will fail without side + effects if invoked from within the execution of an + auto-extension. i.e. auto extensions can neither be + added, removed, nor cleared while one is running. + + See the AutoExtension class docs for more information. + + Achtung: it is as yet unknown whether auto extensions registered + from one JNIEnv (thread) can be safely called from another. + */ + public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); public static int sqlite3_bind_blob(@NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data){ @@ -259,6 +259,12 @@ public final class SQLite3Jni { public static native int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms); + /** + Works like the C API except that it returns false, without side + effects, if auto extensions are currently running. + */ + public static synchronized native boolean sqlite3_cancel_auto_extension(@NotNull AutoExtension ax); + public static native int sqlite3_changes(@NotNull sqlite3 db); public static native long sqlite3_changes64(@NotNull sqlite3 db); @@ -584,6 +590,12 @@ public final class SQLite3Jni { public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + /** + Works like the C API except that it has no side effects if auto + extensions are currently running. + */ + public static synchronized native void sqlite3_reset_auto_extension(); + public static native void sqlite3_result_double(@NotNull sqlite3_context cx, double v); /** diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index e96038d4d8..f956554a4a 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -58,8 +58,13 @@ public class Tester1 { final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; - affirm(0 == rc); sqlite3 db = out.getValue(); + if( 0!=rc ){ + final String msg = db.getNativePointer()==0 + ? sqlite3_errstr(rc) + : sqlite3_errmsg(db); + throw new RuntimeException("Opening db failed: "+msg); + } affirm(0 != db.getNativePointer()); rc = sqlite3_busy_timeout(db, 2000); affirm( 0 == rc ); @@ -979,6 +984,52 @@ public class Tester1 { sqlite3_close(db); } + private static void testAutoExtension(){ + final ValueHolder val = new ValueHolder<>(0); + final ValueHolder toss = new ValueHolder<>(null); + final AutoExtension ax = new AutoExtension(){ + public synchronized int xEntryPoint(sqlite3 db){ + ++val.value; + if( null!=toss.value ){ + throw new RuntimeException(toss.value); + } + return 0; + } + }; + int rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close(createNewDb()); + affirm( 1==val.value ); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + sqlite3_reset_auto_extension(); + sqlite3_close(createNewDb()); + affirm( 2==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + // Must not add a new entry + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + sqlite3_close( createNewDb() ); + affirm( 3==val.value ); + affirm( sqlite3_cancel_auto_extension(ax) ); + affirm( !sqlite3_cancel_auto_extension(ax) ); + sqlite3_close(createNewDb()); + affirm( 3==val.value ); + rc = sqlite3_auto_extension( ax ); + affirm( 0==rc ); + Exception err = null; + toss.value = "Throwing from AutoExtension."; + try{ + createNewDb(); + }catch(Exception e){ + err = e; + } + affirm( err!=null ); + affirm( err.getMessage().indexOf(toss.value)>0 ); + affirm( sqlite3_cancel_auto_extension(ax) ); + } + private static void testSleep(){ out("Sleeping briefly... "); sqlite3_sleep(600); @@ -1013,6 +1064,7 @@ public class Tester1 { testUpdateHook(); testAuthorizer(); testFts5(); + testAutoExtension(); //testSleep(); if(liArgs.indexOf("-v")>0){ sqlite3_do_something_for_developer(); diff --git a/manifest b/manifest index bdb88221fa..0e7118626b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Rework\sthe\ssqlite3_open(_v2)()\sorder\sof\soperations\sso\sthat\spending\sauto-extension\ssupport\scan\sget\sahold\sof\sthe\sopen-time\sJava\sstate\sdespite\sthe\sJava/C\s(sqlite3*)\sbinding\snot\shaving\syet\sbeen\sestablished. -D 2023-08-06T22:09:09.175 +C Bind\sthe\sauto-extension\sAPIs\sto\sJNI. +D 2023-08-07T00:06:31.250 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,9 +232,10 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 21177d7c3492f84aad0666c43ea6d9bc0ebd544f66dcb41219f966b137e86c1b -F ext/jni/src/c/sqlite3-jni.h 2108bb9434fe873e08d6388bce102a474b5c6b00c2ea47d8aee257aca2de2a67 +F ext/jni/src/c/sqlite3-jni.c 406f3fe36661b3628d108a912246b48cafd7fcc1f8b30a563ce6806addb30d71 +F ext/jni/src/c/sqlite3-jni.h 68d219dd351676e819deb38926ebcee0fda141403ce4efa60c3d8bd77993d220 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 +F ext/jni/src/org/sqlite/jni/AutoExtension.java aac84ec21c93306ff5c6ff3b0130de5b76f265dfbc8f7cd158d62cfb8384ff57 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -250,8 +251,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 053ea7dbc1234dd70b8948009a52a3f1 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3edf79fb7a3cb5eaeeaaa074f51521d6d9a962b0dd1ca88074be4fd075258fd8 -F ext/jni/src/org/sqlite/jni/Tester1.java 7ea111e9d52042889f2360e7addfefed4174c1a17dfe6efccf64aaa9a65749cf +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 9af0e0ea79db59d5c4dac13f70031dd5069223d8198f7324f8c1c25e60451e8c +F ext/jni/src/org/sqlite/jni/Tester1.java 63fc2f58b3a5abdad8bd41ff4a1b2572c24fa9246c17a3fcab07dc6adf06ff35 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2082,8 +2083,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 644999caff9db79562d45520d94aaa24ee88c65e397b6fb9c20a4f0e7f84e1a5 -R c4d749cda5fec61bfb97a76dd1b71afd +P 34da294ab558880e81eebd7d261bc590551d5a7d2855e844695cef6394647ea7 +R 8af70828073a693d0f965376a6f73a57 U stephan -Z cb93209a7d6706bee6dfc5cbdd5b5747 +Z 18db46b387e0d752e217b2db93c72ada # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 92f5779fcb..e8b32b32ff 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -34da294ab558880e81eebd7d261bc590551d5a7d2855e844695cef6394647ea7 \ No newline at end of file +746a5fa079ad80b3c59411202ee601e0b5c50e79e5994d5e464fa06d3c276324 \ No newline at end of file From 005baf67d5f3bddef1d9fc9bd74e48d0206da40e Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 00:29:38 +0000 Subject: [PATCH 069/148] Minor internal cleanups and additional test metrics. FossilOrigin-Name: fa0a6b6e8e6c711585bca30357e465f7a2f08a1c7159ecf23031af1e5158b89d --- ext/jni/src/c/sqlite3-jni.c | 42 ++++++++++--------- ext/jni/src/org/sqlite/jni/AutoExtension.java | 6 +-- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 5 +++ ext/jni/src/org/sqlite/jni/Tester1.java | 20 +++++++++ manifest | 18 ++++---- manifest.uuid | 2 +- 6 files changed, 61 insertions(+), 32 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 054b04484f..71e0ab8cf1 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1103,6 +1103,7 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, return rv; } +#if 0 static void PerDbStateJni_dump(PerDbStateJni *s){ MARKER(("PerDbStateJni->env @ %p\n", s->env)); MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb)); @@ -1113,6 +1114,7 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); MARKER(("PerDbStateJni->env @ %p\n", s->env)); } +#endif /** Returns the PerDbStateJni object for the given db. If allocIfNeeded is @@ -1554,7 +1556,8 @@ typedef void (*udf_xFinal_f)(sqlite3_context*); /** State for binding Java-side UDFs. */ -typedef struct { +typedef struct UDFState UDFState; +struct UDFState { JNIEnv * env; /* env registered from */; jobject jObj /* SQLFunction instance */; jclass klazz /* jObj's class */; @@ -1566,7 +1569,7 @@ typedef struct { jmethodID jmidxFinal; jmethodID jmidxValue; jmethodID jmidxInverse; -} UDFState; +}; static UDFState * UDFState_alloc(JNIEnv * const env, jobject jObj){ UDFState * const s = sqlite3_malloc(sizeof(UDFState)); @@ -2019,18 +2022,13 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; PerDbStateJni * ps = 0; assert(version == 1 || version == 2); - if(0){ - PerDbStateJni * s = S3Global.perDb.aUsed; - for( ; s; s = s->pNext){ - PerDbStateJni_dump(s); - } - } ps = PerDbStateJni_for_db(env, jDb, 0, 0); - if(!ps) return rc; - rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); - if(ps) PerDbStateJni_set_aside(ps) - /* MUST come after close() because of ps->trace. */; - NativePointerHolder_set(env, jDb, 0, S3ClassNames.sqlite3); + if(ps){ + rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); + PerDbStateJni_set_aside(ps) + /* MUST come after close() because of ps->trace. */; + NativePointerHolder_set(env, jDb, 0, S3ClassNames.sqlite3); + } return (jint)rc; } @@ -2053,15 +2051,18 @@ static unsigned int s3jni_utf16_strlen(void const * z){ return i; } +/** + sqlite3_collation_needed16() hook impl. + */ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, int eTextRep, const void * z16Name){ PerDbStateJni * const ps = pState; JNIEnv * const env = ps->env; unsigned int const nName = s3jni_utf16_strlen(z16Name); - jstring jName; - jName = (*env)->NewString(env, (jchar const *)z16Name, nName); - IFTHREW { + jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); + IFTHREW{ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); + EXCEPTION_CLEAR; }else{ (*env)->CallVoidMethod(env, ps->collationNeeded.jObj, ps->collationNeeded.midCallback, @@ -2082,7 +2083,8 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ jmethodID xCallback; JniHookState * const pHook = &ps->collationNeeded; int rc; - if(!ps) return SQLITE_MISUSE; + + if( !ps ) return SQLITE_MISUSE; pOld = pHook->jObj; if(pOld && jHook && (*env)->IsSameObject(env, pOld, jHook)){ @@ -3291,13 +3293,15 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ puts("sizeofs:"); #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); - SO(JniHookState); SO(JNIEnvCache); + SO(JniHookState); SO(PerDbStateJni); - SO(S3Global); SO(S3ClassNames); printf("\t(^^^ %u NativePointerHolder subclasses)\n", (unsigned)(sizeof(S3ClassNames) / sizeof(const char *))); + SO(S3Global); + SO(S3JniAutoExtension); + SO(UDFState); printf("Cache info:\n"); printf("\tNativePointerHolder cache: %u misses, %u hits\n", S3Global.metrics.nphCacheMisses, diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java index 9816a967c2..3a58b6589f 100644 --- a/ext/jni/src/org/sqlite/jni/AutoExtension.java +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -21,9 +21,9 @@ public interface AutoExtension { Must function as described for the sqlite3_auto_extension(), with the caveat that the signature is more limited. - As an exception to the callbacks-must-not-throw rule, - AutoExtensions may do so and the exception's error - message will be set as the db's error string. + As an exception (as it were) to the callbacks-must-not-throw + rule, AutoExtensions may do so and the exception's error message + will be set as the db's error string. */ int xEntryPoint(sqlite3 db); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 303ab96662..6a72dd8739 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -475,6 +475,11 @@ public final class SQLite3Jni { heed. Passing the object to sqlite3_close() or sqlite3_close_v2() will clear that pointer mapping. + Recall that even if opening fails, the output pointer might be + non-null. Any error message about the failure will be in that + object and it is up to the caller to sqlite3_close() that + db handle. + Pedantic note: though any number of Java-level sqlite3 objects may refer to/wrap a single C-level (sqlite3*), the JNI internals take a reference to the object which is passed to sqlite3_open() diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index f956554a4a..b6058d0c77 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -1076,6 +1076,26 @@ public class Tester1 { outln("Tests done. Metrics:"); outln("\tAssertions checked: "+affirmCount); outln("\tDatabases opened: "+metrics.dbOpen); + + int nMethods = 0; + int nNatives = 0; + final java.lang.reflect.Method[] declaredMethods = + SQLite3Jni.class.getDeclaredMethods(); + for(java.lang.reflect.Method m : declaredMethods){ + int mod = m.getModifiers(); + if( 0!=(mod & java.lang.reflect.Modifier.STATIC) ){ + final String name = m.getName(); + if(name.startsWith("sqlite3_")){ + ++nMethods; + if( 0!=(mod & java.lang.reflect.Modifier.NATIVE) ){ + ++nNatives; + } + } + } + } + outln("\tSQLite3Jni sqlite3_*() methods: "+ + nNatives+" native methods and "+ + (nMethods - nNatives)+" Java impls"); outln("\tTotal time = " +((timeEnd - timeStart)/1000000.0)+"ms"); } diff --git a/manifest b/manifest index 0e7118626b..51c83aa0b0 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\sthe\sauto-extension\sAPIs\sto\sJNI. -D 2023-08-07T00:06:31.250 +C Minor\sinternal\scleanups\sand\sadditional\stest\smetrics. +D 2023-08-07T00:29:38.785 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,10 +232,10 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 406f3fe36661b3628d108a912246b48cafd7fcc1f8b30a563ce6806addb30d71 +F ext/jni/src/c/sqlite3-jni.c a59464adb77c0d2c539483b4d09b8c547dc97c73cf7f26b4934602c8cd4da28d F ext/jni/src/c/sqlite3-jni.h 68d219dd351676e819deb38926ebcee0fda141403ce4efa60c3d8bd77993d220 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 -F ext/jni/src/org/sqlite/jni/AutoExtension.java aac84ec21c93306ff5c6ff3b0130de5b76f265dfbc8f7cd158d62cfb8384ff57 +F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 @@ -251,8 +251,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java 053ea7dbc1234dd70b8948009a52a3f1 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 9af0e0ea79db59d5c4dac13f70031dd5069223d8198f7324f8c1c25e60451e8c -F ext/jni/src/org/sqlite/jni/Tester1.java 63fc2f58b3a5abdad8bd41ff4a1b2572c24fa9246c17a3fcab07dc6adf06ff35 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e43d17ef7601d6294fa5ba7e15a2be2bcb819b41f4fe0ac069ce45e324b5a928 +F ext/jni/src/org/sqlite/jni/Tester1.java 762c866e3c401f8de2551e8bdc5252f5943693fe6bbbace82c3af984c190a0fd F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2083,8 +2083,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 34da294ab558880e81eebd7d261bc590551d5a7d2855e844695cef6394647ea7 -R 8af70828073a693d0f965376a6f73a57 +P 746a5fa079ad80b3c59411202ee601e0b5c50e79e5994d5e464fa06d3c276324 +R 07d630346a076501319c1423eea49390 U stephan -Z 18db46b387e0d752e217b2db93c72ada +Z bdddfbdfa46e3004c7cc7a21bdb63878 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e8b32b32ff..a91b36d746 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -746a5fa079ad80b3c59411202ee601e0b5c50e79e5994d5e464fa06d3c276324 \ No newline at end of file +fa0a6b6e8e6c711585bca30357e465f7a2f08a1c7159ecf23031af1e5158b89d \ No newline at end of file From 5d48fb1784c241111592117fd918dcd6eaa97115 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 01:06:27 +0000 Subject: [PATCH 070/148] Make sqlite3_stmt() Java ctor private - it's only constructed from JNI code. FossilOrigin-Name: ce82c42f151e38b23945e6f5dd99cb6a77b3c6440508f41abc35e9f6c29cd440 --- ext/jni/src/org/sqlite/jni/OutputPointer.java | 3 +-- ext/jni/src/org/sqlite/jni/sqlite3_stmt.java | 2 ++ manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index 065e133202..c4cc21378d 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -14,8 +14,7 @@ package org.sqlite.jni; /** - Helper classes for handling JNI output pointers for primitive - types. + Helper classes for handling JNI output pointers. We do not use a generic OutputPointer because working with those from the native JNI code is unduly quirky due to a lack of diff --git a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java index fa19572b13..d672301378 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3_stmt.java @@ -20,4 +20,6 @@ package org.sqlite.jni; via JNI. */ public final class sqlite3_stmt extends NativePointerHolder { + // Only invoked from JNI. + private sqlite3_stmt(){} } diff --git a/manifest b/manifest index 51c83aa0b0..ef5812c8c5 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sinternal\scleanups\sand\sadditional\stest\smetrics. -D 2023-08-07T00:29:38.785 +C Make\ssqlite3_stmt()\sJava\sctor\sprivate\s-\sit's\sonly\sconstructed\sfrom\sJNI\scode. +D 2023-08-07T01:06:27.645 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -247,7 +247,7 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java 053ea7dbc1234dd70b8948009a52a3f1090403a6fe2ab7b7885b6f08ed26deea +F ext/jni/src/org/sqlite/jni/OutputPointer.java ebdd33d48064c3302d0d4a6dd345562a967f8420edad7c7509403be277d076a0 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 @@ -262,7 +262,7 @@ F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd9603 F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java ff3729426704626a6019d97bfee512a83f253cf43ffeffbd45b238718154df36 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 -F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 72a0698aeb50a183ad146cd29ee04952abb8c36021f6122656aa5ec20469f6f7 +F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2083,8 +2083,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 746a5fa079ad80b3c59411202ee601e0b5c50e79e5994d5e464fa06d3c276324 -R 07d630346a076501319c1423eea49390 +P fa0a6b6e8e6c711585bca30357e465f7a2f08a1c7159ecf23031af1e5158b89d +R 1b41c977473ab5e66f03fccf5b662909 U stephan -Z bdddfbdfa46e3004c7cc7a21bdb63878 +Z ce9a2d4985cae8c45da2163e3000eab1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a91b36d746..e9dfa5ab7e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fa0a6b6e8e6c711585bca30357e465f7a2f08a1c7159ecf23031af1e5158b89d \ No newline at end of file +ce82c42f151e38b23945e6f5dd99cb6a77b3c6440508f41abc35e9f6c29cd440 \ No newline at end of file From becf29a36c891985ac625a73c164d68718e877c2 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 10:01:59 +0000 Subject: [PATCH 071/148] JNI API renaming to better match the C API. FossilOrigin-Name: 6e0bd03d0ba9ee8422853241ba1c4e963d158d1f042855c0cb0026701907896e --- ext/jni/src/c/sqlite3-jni.c | 71 +++++++++++----------- ext/jni/src/c/sqlite3-jni.h | 8 +-- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 22 +++---- ext/jni/src/org/sqlite/jni/Tester1.java | 10 +-- manifest | 18 +++--- manifest.uuid | 2 +- 6 files changed, 63 insertions(+), 68 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 71e0ab8cf1..ae89c862a9 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -599,6 +599,18 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ } } +/** + Creates a new jByteArray of length nP, copies p's contents into it, and + returns that byte array. + */ +static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ + jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); + if(jba){ + (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); + } + return jba; +} + /** Uses the java.lang.String(byte[],Charset) constructor to create a new String from UTF-8 string z. n is the number of bytes to @@ -623,9 +635,8 @@ static jstring s3jni_utf8_to_jstring(JNIEnvCache * const jc, }else if( z ){ jbyteArray jba; if( n<0 ) n = sqlite3Strlen30(z); - jba = (*env)->NewByteArray(env, (jsize)n); + jba = s3jni_new_jbyteArray(env, (unsigned const char *)z, (jsize)n); if( jba ){ - (*env)->SetByteArrayRegion(env, jba, 0, n, (jbyte const *)z); rv = (*env)->NewObject(env, jc->g.cString, jc->g.ctorStringBA, jba, jc->g.oCharsetUtf8); UNREF_L(jba); @@ -676,6 +687,19 @@ static char * s3jni_jstring_to_utf8(JNIEnvCache * const jc, return rv; } +/** + Expects to be passed a pointer from sqlite3_column_text16() or + sqlite3_value_text16() and a byte-length value from + sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a + Java String of exactly half that character length, returning NULL + if !p or (*env)->NewString() fails. +*/ +static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ + return p + ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) + : NULL; +} + /** Fetches the S3Global.envCache row for the given env, allocing a row if needed. When a row is allocated, its state is initialized @@ -2141,40 +2165,7 @@ JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } -/** - Expects to be passed a pointer from sqlite3_column_text16() or - sqlite3_value_text16() and a length value from - sqlite3_column_bytes16() or sqlite3_value_bytes16(). It creates a - Java String of exactly half that length, returning NULL if !p or - (*env)->NewString() fails. -*/ -static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, int nP){ - return p - ? (*env)->NewString(env, (const jchar *)p, (jsize)(nP/2)) - : NULL; -} - -/** - Creates a new jByteArray of length nP, copies p's contents into it, and - returns that byte array. - */ -static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * const p, int nP){ - jbyteArray jba = (*env)->NewByteArray(env, (jint)nP); - if(jba){ - (*env)->SetByteArrayRegion(env, jba, 0, (jint)nP, (const jbyte*)p); - } - return jba; -} - -JDECL(jstring,1column_1text)(JENV_CSELF, jobject jpStmt, - jint ndx){ - sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); - const int n = sqlite3_column_bytes16(stmt, (int)ndx); - const void * const p = sqlite3_column_text16(stmt, (int)ndx); - return s3jni_text16_to_jstring(env, p, n); -} - -JDECL(jbyteArray,1column_1text_1utf8)(JENV_CSELF, jobject jpStmt, +JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); const int n = sqlite3_column_bytes(stmt, (int)ndx); @@ -2182,6 +2173,14 @@ JDECL(jbyteArray,1column_1text_1utf8)(JENV_CSELF, jobject jpStmt, return s3jni_new_jbyteArray(env, p, n); } +JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, + jint ndx){ + sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); + const int n = sqlite3_column_bytes16(stmt, (int)ndx); + const void * const p = sqlite3_column_text16(stmt, (int)ndx); + return s3jni_text16_to_jstring(env, p, n); +} + JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 01bf381ae0..a8a79df890 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1013,18 +1013,18 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1table_ /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text + * Method: sqlite3_column_text16 * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text16 (JNIEnv *, jclass, jobject, jint); /* * Class: org_sqlite_jni_SQLite3Jni - * Method: sqlite3_column_text_utf8 + * Method: sqlite3_column_text * Signature: (Lorg/sqlite/jni/sqlite3_stmt;I)[B */ -JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text_1utf8 +JNIEXPORT jbyteArray JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1column_1text (JNIEnv *, jclass, jobject, jint); /* diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 6a72dd8739..b3e435fa90 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -326,25 +326,18 @@ public final class SQLite3Jni { public static native String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx); /** - Because Java strings use UTF-16 and JNI speaks Modified UTF-8 - instead of standard UTF8[^1], this routine functions equivalently to - the native sqlite3_column_text16(), so requires conversion from - the db if the db uses the default encoding of UTF-8. - - To extract _standard_ UTF-8, use sqlite3_column_text_utf8(). + To extract _standard_ UTF-8, use sqlite3_column_text(). This API includes no functions for working with Java's Modified UTF-8. - - [^1]: https://stackoverflow.com/questions/7921016 */ - public static native String sqlite3_column_text(@NotNull sqlite3_stmt stmt, int ndx); + public static native String sqlite3_column_text16(@NotNull sqlite3_stmt stmt, int ndx); /** - Similar to sqlite3_column_text(), but the result is an array encoded - in standard UTF-8, not Modified UTF-8. + Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. + Use sqlite3_column_text16() to fetch the text */ - public static native byte[] sqlite3_column_text_utf8(@NotNull sqlite3_stmt stmt, - int ndx); + public static native byte[] sqlite3_column_text(@NotNull sqlite3_stmt stmt, + int ndx); // The real utility of this function is questionable. // /** @@ -455,6 +448,9 @@ public final class SQLite3Jni { public static native String sqlite3_errstr(int resultCode); + /** + Note that the offset values assume UTF-8-encoded SQL. + */ public static native int sqlite3_error_offset(@NotNull sqlite3 db); public static native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index b6058d0c77..b255910614 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -331,7 +331,7 @@ public class Tester1 { StringBuilder sbuf = new StringBuilder(); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ - String txt = sqlite3_column_text(stmt, 0); + String txt = sqlite3_column_text16(stmt, 0); //outln("txt = "+txt); sbuf.append( txt ); ++n; @@ -412,7 +412,7 @@ public class Tester1 { sqlite3_stmt stmt = prepare(db, "SELECT a FROM t ORDER BY a COLLATE reversi"); int counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ - final String val = sqlite3_column_text(stmt, 0); + final String val = sqlite3_column_text16(stmt, 0); ++counter; //outln("REVERSI'd row#"+counter+": "+val); switch(counter){ @@ -426,7 +426,7 @@ public class Tester1 { stmt = prepare(db, "SELECT a FROM t ORDER BY a"); counter = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ - final String val = sqlite3_column_text(stmt, 0); + final String val = sqlite3_column_text16(stmt, 0); ++counter; //outln("Non-REVERSI'd row#"+counter+": "+val); switch(counter){ @@ -634,7 +634,7 @@ public class Tester1 { affirm( 0 == rc ); int n = 0; while( SQLITE_ROW == sqlite3_step(stmt) ){ - final String s = sqlite3_column_text(stmt, 0); + final String s = sqlite3_column_text16(stmt, 0); final int i = sqlite3_column_int(stmt, 1); switch(++n){ case 1: affirm( "a".equals(s) && 9==i ); break; @@ -710,7 +710,7 @@ public class Tester1 { case SQLITE_TRACE_ROW: affirm(pNative instanceof sqlite3_stmt); affirm(null == x); - //outln("TRACE_ROW = "+sqlite3_column_text((sqlite3_stmt)pNative, 0)); + //outln("TRACE_ROW = "+sqlite3_column_text16((sqlite3_stmt)pNative, 0)); break; case SQLITE_TRACE_CLOSE: affirm(pNative instanceof sqlite3); diff --git a/manifest b/manifest index ef5812c8c5..853b14685d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Make\ssqlite3_stmt()\sJava\sctor\sprivate\s-\sit's\sonly\sconstructed\sfrom\sJNI\scode. -D 2023-08-07T01:06:27.645 +C JNI\sAPI\srenaming\sto\sbetter\smatch\sthe\sC\sAPI. +D 2023-08-07T10:01:59.163 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c a59464adb77c0d2c539483b4d09b8c547dc97c73cf7f26b4934602c8cd4da28d -F ext/jni/src/c/sqlite3-jni.h 68d219dd351676e819deb38926ebcee0fda141403ce4efa60c3d8bd77993d220 +F ext/jni/src/c/sqlite3-jni.c e24884ce42955da0effd14c5dacf072cecaf2f10471f83384f43c0ba4ed94805 +F ext/jni/src/c/sqlite3-jni.h c39e30a8e06e93f7b84e3733202f24a75fce16875f3b2f1b89ae28e38e3dfcbb F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -251,8 +251,8 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java ebdd33d48064c3302d0d4a6dd345562a F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java e43d17ef7601d6294fa5ba7e15a2be2bcb819b41f4fe0ac069ce45e324b5a928 -F ext/jni/src/org/sqlite/jni/Tester1.java 762c866e3c401f8de2551e8bdc5252f5943693fe6bbbace82c3af984c190a0fd +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7f15c3d17d61c19d51dfa32d810707eaa8c9149c05a364ad01fba6c2a6bda6f7 +F ext/jni/src/org/sqlite/jni/Tester1.java 55bf9c35c4a5649bdfb6ce940117d33ec24a6722bc252fadf7bc7102b9e94d6a F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2083,8 +2083,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P fa0a6b6e8e6c711585bca30357e465f7a2f08a1c7159ecf23031af1e5158b89d -R 1b41c977473ab5e66f03fccf5b662909 +P ce82c42f151e38b23945e6f5dd99cb6a77b3c6440508f41abc35e9f6c29cd440 +R 7eccdfc2ef7173451e58d94e7f4df988 U stephan -Z ce9a2d4985cae8c45da2163e3000eab1 +Z da2d66afb2e1f6b314be079f14f3192a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e9dfa5ab7e..507b2c53f0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ce82c42f151e38b23945e6f5dd99cb6a77b3c6440508f41abc35e9f6c29cd440 \ No newline at end of file +6e0bd03d0ba9ee8422853241ba1c4e963d158d1f042855c0cb0026701907896e \ No newline at end of file From be7aef1f85b6898b74c27cfe0d64aee39dc1c378 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 10:59:27 +0000 Subject: [PATCH 072/148] Lots of JNI internal API renaming, for consistency, and moving-around of utility functions. Make it safe for more callback types to throw. FossilOrigin-Name: 9a494394b9eb28cf88dc5e7075a4b8c682c8e14fdd6837b595bec8011d7e9e72 --- ext/jni/src/c/sqlite3-jni.c | 999 +++++++++--------- ext/jni/src/c/sqlite3-jni.h | 2 +- .../src/org/sqlite/jni/CollationNeeded.java | 8 +- .../src/org/sqlite/jni/ProgressHandler.java | 4 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 4 + manifest | 20 +- manifest.uuid | 2 +- 7 files changed, 534 insertions(+), 505 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index ae89c862a9..b83e50b82c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -189,21 +189,29 @@ /** Helpers for extracting pointers from jobjects, noting that the corresponding Java interfaces have already done the type-checking. */ -#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3) -#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3_stmt) -#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3_value) -#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.sqlite3_context) +#define PtrGet_sqlite3(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3) +#define PtrGet_sqlite3_stmt(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_stmt) +#define PtrGet_sqlite3_value(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_value) +#define PtrGet_sqlite3_context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.sqlite3_context) /* Helpers for Java value reference management. */ -static inline jobject new_global_ref(JNIEnv *env, jobject v){ +static inline jobject new_global_ref(JNIEnv * const env, jobject const v){ return v ? (*env)->NewGlobalRef(env, v) : NULL; } +static inline void delete_global_ref(JNIEnv * const env, jobject const v){ + if(v) (*env)->DeleteGlobalRef(env, v); +} +static inline void delete_local_ref(JNIEnv * const env, jobject const v){ + if(v) (*env)->DeleteLocalRef(env, v); +} #define REF_G(VAR) new_global_ref(env, (VAR)) #define REF_L(VAR) (*env)->NewLocalRef(env, VAR) -#define UNREF_G(VAR) if(VAR) (*env)->DeleteGlobalRef(env, (VAR)) -#define UNREF_L(VAR) if(VAR) (*env)->DeleteLocalRef(env, (VAR)) +#define UNREF_G(VAR) delete_global_ref(env,(VAR)) +#define UNREF_L(VAR) delete_local_ref(env,(VAR)) /** - Constant string class names used as keys for S3Global_nph_cache() and + Constant string class names used as keys for S3JniGlobal_nph_cache(), +S3Jni + and friends. */ static const struct { @@ -224,7 +232,7 @@ static const struct { const char * const fts5_tokenizer; const char * const Fts5Tokenizer; #endif -} S3ClassNames = { +} S3JniClassNames = { "org/sqlite/jni/sqlite3", "org/sqlite/jni/sqlite3_stmt", "org/sqlite/jni/sqlite3_context", @@ -311,20 +319,20 @@ enum { (only) the library's NativePointerHolder types, a fixed count known at build-time. If we add more than this a fatal error will be triggered with a reminder to increase this. This value needs - to be exactly the number of entries in the S3ClassNames - object. The S3ClassNames entries are the keys for this particular + to be exactly the number of entries in the S3JniClassNames + object. The S3JniClassNames entries are the keys for this particular cache. */ - NphCache_SIZE = sizeof(S3ClassNames) / sizeof(char const *) + NphCache_SIZE = sizeof(S3JniClassNames) / sizeof(char const *) }; /** Cache entry for NativePointerHolder lookups. */ -typedef struct NphCacheLine NphCacheLine; -struct NphCacheLine { +typedef struct S3JniNphCache S3JniNphCache; +struct S3JniNphCache { const char * zClassName /* "full/class/Name". Must be a static - string pointer from the S3ClassNames + string pointer from the S3JniClassNames struct. */; jclass klazz /* global ref to the concrete NativePointerHolder subclass represented by @@ -348,8 +356,8 @@ struct NphCacheLine { Whereas we cache new refs for each thread. */ -typedef struct JNIEnvCache JNIEnvCache; -struct JNIEnvCache { +typedef struct S3JniEnvCache S3JniEnvCache; +struct S3JniEnvCache { JNIEnv *env /* env in which this cache entry was created */; //! The various refs to global classes might be cacheable a single // time globally. Information online seems inconsistent on that @@ -362,7 +370,7 @@ struct JNIEnvCache { jmethodID ctorLong1 /* the Long(long) constructor */; jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; jmethodID stringGetBytes /* the String.getBytes(Charset) method */; - } g; + } g /* refs to global Java state */; jobject currentStmt /* Current Java sqlite3_stmt object being prepared, stepped, reset, or finalized. Needed for tracing, the @@ -381,20 +389,22 @@ struct JNIEnvCache { jfieldID fidB; } jPhraseIter; #endif - JNIEnvCache * pPrev /* Previous entry in the linked list */; - JNIEnvCache * pNext /* Next entry in the linked list */; - /** TODO: NphCacheLine *pNphHit; + S3JniEnvCache * pPrev /* Previous entry in the linked list */; + S3JniEnvCache * pNext /* Next entry in the linked list */; + /** TODO?: S3JniNphCache *pNphHit; - to help fast-track cache lookups, update this to point to the - most recent hit. That will speed up, e.g. the - sqlite3_value-to-Java-array loop. + and always set it to the most recent cache search result. + + The intent would be to help fast-track cache lookups and would + speed up, e.g., the sqlite3_value-to-Java-array loop in a + multi-threaded app. */ - struct NphCacheLine nph[NphCache_SIZE]; + S3JniNphCache nph[NphCache_SIZE]; }; -static void NphCacheLine_clear(JNIEnv * const env, NphCacheLine * const p){ +static void S3JniNphCache_clear(JNIEnv * const env, S3JniNphCache * const p){ UNREF_G(p->klazz); - memset(p, 0, sizeof(NphCacheLine)); + memset(p, 0, sizeof(S3JniNphCache)); } #define S3JNI_ENABLE_AUTOEXT 1 @@ -430,8 +440,8 @@ struct S3JniAutoExtension { #endif /** State for various hook callbacks. */ -typedef struct JniHookState JniHookState; -struct JniHookState{ +typedef struct S3JniHookState S3JniHookState; +struct S3JniHookState{ jobject jObj /* global ref to Java instance */; jmethodID midCallback /* callback method. Signature depends on jObj's type */; @@ -446,8 +456,8 @@ struct JniHookState{ allocated as needed, cleaned up in sqlite3_close(_v2)(), and recycled when possible. It is freed during sqlite3_shutdown(). */ -typedef struct PerDbStateJni PerDbStateJni; -struct PerDbStateJni { +typedef struct S3JniDb S3JniDb; +struct S3JniDb { JNIEnv *env /* The associated JNIEnv handle */; sqlite3 *pDb /* The associated db handle */; jobject jDb /* A global ref of the object which was passed to @@ -460,22 +470,25 @@ struct PerDbStateJni { to receive. */; char * zMainDbName /* Holds any string allocated on behave of SQLITE_DBCONFIG_MAINDBNAME. */; - JniHookState busyHandler; - JniHookState collation; - JniHookState collationNeeded; - JniHookState commitHook; - JniHookState progress; - JniHookState rollbackHook; - JniHookState trace; - JniHookState updateHook; - JniHookState authHook; + S3JniHookState busyHandler; + S3JniHookState collation; + S3JniHookState collationNeeded; + S3JniHookState commitHook; + S3JniHookState progress; + S3JniHookState rollbackHook; + S3JniHookState trace; + S3JniHookState updateHook; + S3JniHookState authHook; #ifdef SQLITE_ENABLE_FTS5 jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; #endif - PerDbStateJni * pNext /* Next entry in the available/free list */; - PerDbStateJni * pPrev /* Previous entry in the available/free list */; + S3JniDb * pNext /* Next entry in the available/free list */; + S3JniDb * pPrev /* Previous entry in the available/free list */; }; +/** + Global state, e.g. caches and metrics. +*/ static struct { /** According to: https://developer.ibm.com/articles/j-jni/ @@ -490,12 +503,12 @@ static struct { */ JavaVM * jvm; struct { - JNIEnvCache * aHead /* Linked list of in-use instances */; - JNIEnvCache * aFree /* Linked list of free instances */; + S3JniEnvCache * aHead /* Linked list of in-use instances */; + S3JniEnvCache * aFree /* Linked list of free instances */; } envCache; struct { - PerDbStateJni * aUsed /* Linked list of in-use instances */; - PerDbStateJni * aFree /* Linked list of free instances */; + S3JniDb * aUsed /* Linked list of in-use instances */; + S3JniDb * aFree /* Linked list of free instances */; } perDb; struct { unsigned nphCacheHits; @@ -515,7 +528,7 @@ static struct { #if S3JNI_ENABLE_AUTOEXT struct { S3JniAutoExtension *pHead /* Head of the auto-extension list */; - PerDbStateJni * psOpening /* handle to the being-opened db. We + S3JniDb * psOpening /* handle to the being-opened db. We need this so that auto extensions can have a consistent view of the cross-language db connection and @@ -528,7 +541,7 @@ static struct { running. */; } autoExt; #endif -} S3Global; +} S3JniGlobal; /** sqlite3_malloc() proxy which fails fatally on OOM. This should @@ -544,6 +557,77 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){ return rv; } +/** + Fetches the S3JniGlobal.envCache row for the given env, allocing + a row if needed. When a row is allocated, its state is initialized + insofar as possible. Calls (*env)->FatalError() if allocation of + an entry fails. That's hypothetically possible but "shouldn't happen." +*/ +FIXME_THREADING +static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ + struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; + for( ; row; row = row->pNext ){ + if( row->env == env ){ + ++S3JniGlobal.metrics.envCacheHits; + return row; + } + } + ++S3JniGlobal.metrics.envCacheMisses; + row = S3JniGlobal.envCache.aFree; + if( row ){ + assert(!row->pPrev); + S3JniGlobal.envCache.aFree = row->pNext; + if( row->pNext ) row->pNext->pPrev = 0; + }else{ + row = sqlite3_malloc(sizeof(S3JniEnvCache)); + if( !row ){ + (*env)->FatalError(env, "Maintenance required: S3JniEnvCache is full.") + /* Does not return, but cc doesn't know that */; + return NULL; + } + } + memset(row, 0, sizeof(*row)); + row->pNext = S3JniGlobal.envCache.aHead; + if(row->pNext) row->pNext->pPrev = row; + S3JniGlobal.envCache.aHead = row; + row->env = env; + + /* Grab references to various global classes and objects... */ + row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); + EXCEPTION_IS_FATAL("Error getting reference to Object class."); + + row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); + EXCEPTION_IS_FATAL("Error getting reference to Long class."); + row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, + "", "(J)V"); + EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); + + row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); + EXCEPTION_IS_FATAL("Error getting reference to String class."); + row->g.ctorStringBA = + (*env)->GetMethodID(env, row->g.cString, + "", "([BLjava/nio/charset/Charset;)V"); + EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); + row->g.stringGetBytes = + (*env)->GetMethodID(env, row->g.cString, + "getBytes", "(Ljava/nio/charset/Charset;)[B"); + EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); + + { /* StandardCharsets.UTF_8 */ + jfieldID fUtf8; + jclass const klazzSC = + (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); + EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); + fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", + "Ljava/nio/charset/Charset;"); + EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); + row->g.oCharsetUtf8 = + REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); + EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); + } + return row; +} + /* ** This function is NOT part of the sqlite3 public API. It is strictly ** for use by the sqlite project's own Java/JNI bindings. @@ -570,35 +654,6 @@ static int s3jni_db_error(sqlite3* const db, int err_code, const char * const zM return err_code; } -/** - Extracts the (void xDestroy()) method from the given jclass and - applies it to jobj. If jObj is NULL, this is a no-op. If klazz is - NULL then it's derived from jobj. The lack of an xDestroy() method - is silently ignored and any exceptions thrown by the method trigger - a warning to stdout or stderr and then the exception is suppressed. -*/ -static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ - if(jObj){ - jmethodID method; - if(!klazz){ - klazz = (*env)->GetObjectClass(env, jObj); - assert(klazz); - } - method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); - //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); - if(method){ - ++S3Global.metrics.nDestroy; - (*env)->CallVoidMethod(env, jObj, method); - IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); - EXCEPTION_CLEAR; - } - }else{ - EXCEPTION_CLEAR; - } - } -} - /** Creates a new jByteArray of length nP, copies p's contents into it, and returns that byte array. @@ -623,7 +678,7 @@ static jbyteArray s3jni_new_jbyteArray(JNIEnv * const env, const unsigned char * standard UTF-8 to a Java string, but JNI offers only algorithms for working with MUTF-8, not UTF-8. */ -static jstring s3jni_utf8_to_jstring(JNIEnvCache * const jc, +static jstring s3jni_utf8_to_jstring(S3JniEnvCache * const jc, const char * const z, int n){ jstring rv = NULL; JNIEnv * const env = jc->env; @@ -660,7 +715,7 @@ static jstring s3jni_utf8_to_jstring(JNIEnvCache * const jc, The returned memory is allocated from sqlite3_malloc() and ownership is transferred to the caller. */ -static char * s3jni_jstring_to_utf8(JNIEnvCache * const jc, +static char * s3jni_jstring_to_utf8(S3JniEnvCache * const jc, jstring jstr, int *nLen){ JNIEnv * const env = jc->env; jbyteArray jba; @@ -700,77 +755,6 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, : NULL; } -/** - Fetches the S3Global.envCache row for the given env, allocing - a row if needed. When a row is allocated, its state is initialized - insofar as possible. Calls (*env)->FatalError() if allocation of - an entry fails. That's hypothetically possible but "shouldn't happen." -*/ -FIXME_THREADING -static JNIEnvCache * S3Global_env_cache(JNIEnv * const env){ - struct JNIEnvCache * row = S3Global.envCache.aHead; - for( ; row; row = row->pNext ){ - if( row->env == env ){ - ++S3Global.metrics.envCacheHits; - return row; - } - } - ++S3Global.metrics.envCacheMisses; - row = S3Global.envCache.aFree; - if( row ){ - assert(!row->pPrev); - S3Global.envCache.aFree = row->pNext; - if( row->pNext ) row->pNext->pPrev = 0; - }else{ - row = sqlite3_malloc(sizeof(JNIEnvCache)); - if( !row ){ - (*env)->FatalError(env, "Maintenance required: JNIEnvCache is full.") - /* Does not return, but cc doesn't know that */; - return NULL; - } - } - memset(row, 0, sizeof(*row)); - row->pNext = S3Global.envCache.aHead; - if(row->pNext) row->pNext->pPrev = row; - S3Global.envCache.aHead = row; - row->env = env; - - /* Grab references to various global classes and objects... */ - row->g.cObj = REF_G((*env)->FindClass(env,"java/lang/Object")); - EXCEPTION_IS_FATAL("Error getting reference to Object class."); - - row->g.cLong = REF_G((*env)->FindClass(env,"java/lang/Long")); - EXCEPTION_IS_FATAL("Error getting reference to Long class."); - row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong, - "", "(J)V"); - EXCEPTION_IS_FATAL("Error getting reference to Long constructor."); - - row->g.cString = REF_G((*env)->FindClass(env,"java/lang/String")); - EXCEPTION_IS_FATAL("Error getting reference to String class."); - row->g.ctorStringBA = - (*env)->GetMethodID(env, row->g.cString, - "", "([BLjava/nio/charset/Charset;)V"); - EXCEPTION_IS_FATAL("Error getting reference to String(byte[],Charset) ctor."); - row->g.stringGetBytes = - (*env)->GetMethodID(env, row->g.cString, - "getBytes", "(Ljava/nio/charset/Charset;)[B"); - EXCEPTION_IS_FATAL("Error getting reference to String.getBytes(Charset)."); - - { /* StandardCharsets.UTF_8 */ - jfieldID fUtf8; - jclass const klazzSC = - (*env)->FindClass(env,"java/nio/charset/StandardCharsets"); - EXCEPTION_IS_FATAL("Error getting reference to StndardCharsets class."); - fUtf8 = (*env)->GetStaticFieldID(env, klazzSC, "UTF_8", - "Ljava/nio/charset/Charset;"); - EXCEPTION_IS_FATAL("Error getting StndardCharsets.UTF_8 field."); - row->g.oCharsetUtf8 = - REF_G((*env)->GetStaticObjectField(env, klazzSC, fUtf8)); - EXCEPTION_IS_FATAL("Error getting reference to StandardCharsets.UTF_8."); - } - return row; -} - /** Requires jx to be a Throwable. Calls its getMessage() method and returns its value converted to a UTF-8 string. The caller owns the @@ -778,12 +762,11 @@ static JNIEnvCache * S3Global_env_cache(JNIEnv * const env){ if there is a problem fetching the info or on OOM. */ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jmethodID mid; jstring msg; char * zMsg; jclass const klazz = (*env)->GetObjectClass(env, jx); - EXCEPTION_IS_FATAL("Cannot get class of exception object."); mid = (*env)->GetMethodID(env, klazz, "getMessage", "()Ljava/lang/String;"); IFTHREW{ EXCEPTION_REPORT; @@ -801,6 +784,63 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ return zMsg; } +/** + Extracts the current JNI exception, sets ps->pDb's error message to + its message string, and clears the exception. If errCode is non-0, + it is used as-is, else SQLITE_ERROR is assumed. If there's a + problem extracting the exception's message, it's treated as + non-fatal and zDfltMsg is used in its place. + + This must only be called if a JNI exception is pending. + + Returns errCode unless it is 0, in which case SQLITE_ERROR is + returned. +*/ +static int s3jni_db_exception(JNIEnv * const env, S3JniDb * const ps, + int errCode, const char *zDfltMsg){ + jthrowable const ex = (*env)->ExceptionOccurred(env); + + if( 0==errCode ) errCode = SQLITE_ERROR; + if( ex ){ + char * zMsg; + EXCEPTION_CLEAR; + zMsg = s3jni_exception_error_msg(env, ex); + s3jni_db_error(ps->pDb, errCode, zMsg ? zMsg : zDfltMsg); + sqlite3_free(zMsg); + UNREF_L(ex); + } + return errCode; +} + +/** + Extracts the (void xDestroy()) method from the given jclass and + applies it to jobj. If jObj is NULL, this is a no-op. If klazz is + NULL then it's derived from jobj. The lack of an xDestroy() method + is silently ignored and any exceptions thrown by the method trigger + a warning to stdout or stderr and then the exception is suppressed. +*/ +static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ + if(jObj){ + jmethodID method; + if(!klazz){ + klazz = (*env)->GetObjectClass(env, jObj); + assert(klazz); + } + method = (*env)->GetMethodID(env, klazz, "xDestroy", "()V"); + //MARKER(("jObj=%p, klazz=%p, method=%p\n", jObj, klazz, method)); + if(method){ + ++S3JniGlobal.metrics.nDestroy; + (*env)->CallVoidMethod(env, jObj, method); + IFTHREW{ + EXCEPTION_WARN_CALLBACK_THREW("xDestroy() callback"); + EXCEPTION_CLEAR; + } + }else{ + EXCEPTION_CLEAR; + } + } +} + /** Removes any Java references from s and clears its state. If doXDestroy is true and s->klazz and s->jObj are not NULL, s->jObj's @@ -808,7 +848,7 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ cleared. It is legal to call this when the object has no Java references. */ -static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int doXDestroy){ +static void S3JniHookState_unref(JNIEnv * const env, S3JniHookState * const s, int doXDestroy){ if(doXDestroy && s->klazz && s->jObj){ s3jni_call_xDestroy(env, s->jObj, s->klazz); } @@ -821,7 +861,7 @@ static void JniHookState_unref(JNIEnv * const env, JniHookState * const s, int d Clears s's state and moves it to the free-list. */ FIXME_THREADING -static void PerDbStateJni_set_aside(PerDbStateJni * const s){ +static void S3JniDb_set_aside(S3JniDb * const s){ if(s){ JNIEnv * const env = s->env; assert(s->pDb && "Else this object is already in the free-list."); @@ -831,12 +871,12 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); if(s->pNext) s->pNext->pPrev = s->pPrev; if(s->pPrev) s->pPrev->pNext = s->pNext; - else if(S3Global.perDb.aUsed == s){ + else if(S3JniGlobal.perDb.aUsed == s){ assert(!s->pPrev); - S3Global.perDb.aUsed = s->pNext; + S3JniGlobal.perDb.aUsed = s->pNext; } sqlite3_free( s->zMainDbName ); -#define UNHOOK(MEMBER,XDESTROY) JniHookState_unref(env, &s->MEMBER, XDESTROY) +#define UNHOOK(MEMBER,XDESTROY) S3JniHookState_unref(env, &s->MEMBER, XDESTROY) UNHOOK(trace, 0); UNHOOK(progress, 0); UNHOOK(commitHook, 0); @@ -851,10 +891,10 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ #ifdef SQLITE_ENABLE_FTS5 UNREF_G(s->jFtsApi); #endif - memset(s, 0, sizeof(PerDbStateJni)); - s->pNext = S3Global.perDb.aFree; + memset(s, 0, sizeof(S3JniDb)); + s->pNext = S3JniGlobal.perDb.aFree; if(s->pNext) s->pNext->pPrev = s; - S3Global.perDb.aFree = s; + S3JniGlobal.perDb.aFree = s; //MARKER(("%p->pPrev@%p, pNext@%p\n", s, s->pPrev, s->pNext)); //if(s->pNext) MARKER(("next: %p->pPrev@%p\n", s->pNext, s->pNext->pPrev)); } @@ -864,7 +904,7 @@ static void PerDbStateJni_set_aside(PerDbStateJni * const s){ Requires that p has been snipped from any linked list it is in. Clears all Java refs p holds and zeroes out p. */ -static void JNIEnvCache_clear(JNIEnvCache * const p){ +static void S3JniEnvCache_clear(S3JniEnvCache * const p){ JNIEnv * const env = p->env; if(env){ int i; @@ -878,26 +918,26 @@ static void JNIEnvCache_clear(JNIEnvCache * const p){ UNREF_G(p->jPhraseIter.klazz); #endif for( i = 0; i < NphCache_SIZE; ++i ){ - NphCacheLine_clear(env, &p->nph[i]); + S3JniNphCache_clear(env, &p->nph[i]); } - memset(p, 0, sizeof(JNIEnvCache)); + memset(p, 0, sizeof(S3JniEnvCache)); } } /** - Cleans up all state in S3Global.perDb for th given JNIEnv. + Cleans up all state in S3JniGlobal.perDb for th given JNIEnv. Results are undefined if a Java-side db uses the API from the given JNIEnv after this call. */ FIXME_THREADING -static void PerDbStateJni_free_for_env(JNIEnv *env){ - PerDbStateJni * ps = S3Global.perDb.aUsed; - PerDbStateJni * pNext = 0; +static void S3JniDb_free_for_env(JNIEnv *env){ + S3JniDb * ps = S3JniGlobal.perDb.aUsed; + S3JniDb * pNext = 0; for( ; ps; ps = pNext ){ pNext = ps->pNext; if(ps->env == env){ - PerDbStateJni * const pPrev = ps->pPrev; - PerDbStateJni_set_aside(ps); + S3JniDb * const pPrev = ps->pPrev; + S3JniDb_set_aside(ps); assert(pPrev ? pPrev->pNext!=ps : 1); pNext = pPrev; } @@ -909,11 +949,11 @@ static void PerDbStateJni_free_for_env(JNIEnv *env){ references the cache owns. Returns true if env was cached and false if it was not found in the cache. - Also passes env to PerDbStateJni_free_for_env() to free up + Also passes env to S3JniDb_free_for_env() to free up what would otherwise be stale references. */ -static int S3Global_env_uncache(JNIEnv * const env){ - struct JNIEnvCache * row = S3Global.envCache.aHead; +static int S3JniGlobal_env_uncache(JNIEnv * const env){ + struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ if( row->env == env ){ break; @@ -922,23 +962,23 @@ static int S3Global_env_uncache(JNIEnv * const env){ if( !row ) return 0; if( row->pNext ) row->pNext->pPrev = row->pPrev; if( row->pPrev ) row->pPrev->pNext = row->pNext; - if( S3Global.envCache.aHead == row ){ + if( S3JniGlobal.envCache.aHead == row ){ assert( !row->pPrev ); - S3Global.envCache.aHead = row->pNext; + S3JniGlobal.envCache.aHead = row->pNext; } - JNIEnvCache_clear(row); + S3JniEnvCache_clear(row); assert( !row->pNext ); assert( !row->pPrev ); - row->pNext = S3Global.envCache.aFree; + row->pNext = S3JniGlobal.envCache.aFree; if( row->pNext ) row->pNext->pPrev = row; - S3Global.envCache.aFree = row; - PerDbStateJni_free_for_env(env); + S3JniGlobal.envCache.aFree = row; + S3JniDb_free_for_env(env); return 1; } -static void S3Global_JNIEnvCache_clear(void){ - while( S3Global.envCache.aHead ){ - S3Global_env_uncache( S3Global.envCache.aHead->env ); +static void S3JniGlobal_S3JniEnvCache_clear(void){ + while( S3JniGlobal.envCache.aHead ){ + S3JniGlobal_env_uncache( S3JniGlobal.envCache.aHead->env ); } } @@ -959,7 +999,7 @@ static void S3Global_JNIEnvCache_clear(void){ (2023-07-31) tests. */ FIXME_THREADING -static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char *zClassName){ +static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ /** According to: @@ -975,25 +1015,25 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char * looking up class objects can be expensive, so they should be cached as well. */ - struct JNIEnvCache * const envRow = S3Global_env_cache(env); - struct NphCacheLine * freeSlot = 0; - struct NphCacheLine * cacheLine = 0; + struct S3JniEnvCache * const envRow = S3JniGlobal_env_cache(env); + S3JniNphCache * freeSlot = 0; + S3JniNphCache * pCache = 0; int i; assert(envRow); for( i = 0; i < NphCache_SIZE; ++i ){ - cacheLine = &envRow->nph[i]; - if(zClassName == cacheLine->zClassName){ - ++S3Global.metrics.nphCacheHits; + pCache = &envRow->nph[i]; + if(zClassName == pCache->zClassName){ + ++S3JniGlobal.metrics.nphCacheHits; #define DUMP_NPH_CACHES 0 #if DUMP_NPH_CACHES MARKER(("Cache hit #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3Global.metrics.nphCacheHits, zClassName, cacheLine->klazz, cacheLine->fidValue, - cacheLine->midCtor)); + S3JniGlobal.metrics.nphCacheHits, zClassName, pCache->klazz, pCache->fidValue, + pCache->midCtor)); #endif - assert(cacheLine->klazz); - return cacheLine; - }else if(!freeSlot && !cacheLine->zClassName){ - freeSlot = cacheLine; + assert(pCache->klazz); + return pCache; + }else if(!freeSlot && !pCache->zClassName){ + freeSlot = pCache; } } if(freeSlot){ @@ -1001,11 +1041,11 @@ static struct NphCacheLine * S3Global_nph_cache(JNIEnv * const env, const char * freeSlot->klazz = (*env)->FindClass(env, zClassName); EXCEPTION_IS_FATAL("FindClass() unexpectedly threw"); freeSlot->klazz = REF_G(freeSlot->klazz); - ++S3Global.metrics.nphCacheMisses; + ++S3JniGlobal.metrics.nphCacheMisses; #if DUMP_NPH_CACHES static unsigned int cacheMisses = 0; MARKER(("Cache miss #%u %s klazz@%p nativePointer field@%p, ctor@%p\n", - S3Global.metrics.nphCacheMisses, zClassName, freeSlot->klazz, + S3JniGlobal.metrics.nphCacheMisses, zClassName, freeSlot->klazz, freeSlot->fidValue, freeSlot->midCtor)); #endif #undef DUMP_NPH_CACHES @@ -1033,20 +1073,20 @@ static jfieldID NativePointerHolder_getField(JNIEnv * const env, jclass klazz){ static void NativePointerHolder_set(JNIEnv * env, jobject ppOut, const void * p, const char *zClassName){ jfieldID setter = 0; - struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); - if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ - assert(zClassName == cacheLine->zClassName); - setter = cacheLine->fidValue; + S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->klazz && pCache->fidValue){ + assert(zClassName == pCache->zClassName); + setter = pCache->fidValue; assert(setter); }else{ jclass const klazz = - cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, ppOut); + pCache ? pCache->klazz : (*env)->GetObjectClass(env, ppOut); setter = NativePointerHolder_getField(env, klazz); - if(cacheLine){ - assert(cacheLine->klazz); - assert(!cacheLine->fidValue); - assert(zClassName == cacheLine->zClassName); - cacheLine->fidValue = setter; + if(pCache){ + assert(pCache->klazz); + assert(!pCache->fidValue); + assert(zClassName == pCache->zClassName); + pCache->fidValue = setter; } } (*env)->SetLongField(env, ppOut, setter, (jlong)p); @@ -1062,17 +1102,17 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC if( pObj ){ jfieldID getter = 0; void * rv = 0; - struct NphCacheLine * const cacheLine = S3Global_nph_cache(env, zClassName); - if(cacheLine && cacheLine->fidValue){ - getter = cacheLine->fidValue; + S3JniNphCache * const pCache = S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->fidValue){ + getter = pCache->fidValue; }else{ jclass const klazz = - cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, pObj); + pCache ? pCache->klazz : (*env)->GetObjectClass(env, pObj); getter = NativePointerHolder_getField(env, klazz); - if(cacheLine){ - assert(cacheLine->klazz); - assert(zClassName == cacheLine->zClassName); - cacheLine->fidValue = getter; + if(pCache){ + assert(pCache->klazz); + assert(zClassName == pCache->zClassName); + pCache->fidValue = getter; } } rv = (void*)(*env)->GetLongField(env, pObj, getter); @@ -1084,19 +1124,19 @@ static void * NativePointerHolder_get(JNIEnv * env, jobject pObj, const char *zC } /** - Extracts the new PerDbStateJni instance from the free-list, or + Extracts the new S3JniDb instance from the free-list, or allocates one if needed, associats it with pDb, and returns. Returns NULL on OOM. pDb MUST be associated with jDb via NativePointerHolder_set(). */ -static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, - jobject jDb){ - PerDbStateJni * rv; - if(S3Global.perDb.aFree){ - rv = S3Global.perDb.aFree; +static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb, + jobject jDb){ + S3JniDb * rv; + if(S3JniGlobal.perDb.aFree){ + rv = S3JniGlobal.perDb.aFree; //MARKER(("state@%p for db allocating for db@%p from free-list\n", rv, pDb)); //MARKER(("%p->pPrev@%p, pNext@%p\n", rv, rv->pPrev, rv->pNext)); - S3Global.perDb.aFree = rv->pNext; + S3JniGlobal.perDb.aFree = rv->pNext; assert(rv->pNext != rv); assert(rv->pPrev != rv); assert(rv->pPrev ? (rv->pPrev!=rv->pNext) : 1); @@ -1107,15 +1147,15 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, rv->pNext = 0; } }else{ - rv = s3jni_malloc(env, sizeof(PerDbStateJni)); + rv = s3jni_malloc(env, sizeof(S3JniDb)); //MARKER(("state@%p for db allocating for db@%p from heap\n", rv, pDb)); if(rv){ - memset(rv, 0, sizeof(PerDbStateJni)); + memset(rv, 0, sizeof(S3JniDb)); } } if(rv){ - rv->pNext = S3Global.perDb.aUsed; - S3Global.perDb.aUsed = rv; + rv->pNext = S3JniGlobal.perDb.aUsed; + S3JniGlobal.perDb.aUsed = rv; if(rv->pNext){ assert(!rv->pNext->pPrev); rv->pNext->pPrev = rv; @@ -1128,27 +1168,27 @@ static PerDbStateJni * PerDbStateJni_alloc(JNIEnv * const env, sqlite3 *pDb, } #if 0 -static void PerDbStateJni_dump(PerDbStateJni *s){ - MARKER(("PerDbStateJni->env @ %p\n", s->env)); - MARKER(("PerDbStateJni->pDb @ %p\n", s->pDb)); - MARKER(("PerDbStateJni->trace.jObj @ %p\n", s->trace.jObj)); - MARKER(("PerDbStateJni->progress.jObj @ %p\n", s->progress.jObj)); - MARKER(("PerDbStateJni->commitHook.jObj @ %p\n", s->commitHook.jObj)); - MARKER(("PerDbStateJni->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); - MARKER(("PerDbStateJni->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); - MARKER(("PerDbStateJni->env @ %p\n", s->env)); +static void S3JniDb_dump(S3JniDb *s){ + MARKER(("S3JniDb->env @ %p\n", s->env)); + MARKER(("S3JniDb->pDb @ %p\n", s->pDb)); + MARKER(("S3JniDb->trace.jObj @ %p\n", s->trace.jObj)); + MARKER(("S3JniDb->progress.jObj @ %p\n", s->progress.jObj)); + MARKER(("S3JniDb->commitHook.jObj @ %p\n", s->commitHook.jObj)); + MARKER(("S3JniDb->rollbackHook.jObj @ %p\n", s->rollbackHook.jObj)); + MARKER(("S3JniDb->busyHandler.jObj @ %p\n", s->busyHandler.jObj)); + MARKER(("S3JniDb->env @ %p\n", s->env)); } #endif /** - Returns the PerDbStateJni object for the given db. If allocIfNeeded is + Returns the S3JniDb object for the given db. If allocIfNeeded is true then a new instance will be allocated if no mapping currently exists, else NULL is returned if no mapping is found. The 3rd and 4th args should normally only be non-0 for sqlite3_open(_v2)(). For most other cases, they must be 0, in which case the db handle will be fished out of the jDb object and NULL is - returned if jDb does not have any associated PerDbStateJni. + returned if jDb does not have any associated S3JniDb. If called with a NULL jDb and non-NULL pDb then allocIfNeeded MUST be false and it will look for a matching db object. That usage is @@ -1156,9 +1196,9 @@ static void PerDbStateJni_dump(PerDbStateJni *s){ return a (sqlite3*) but do not take one as an argument. */ FIXME_THREADING -static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, - sqlite3 *pDb, int allocIfNeeded){ - PerDbStateJni * s = S3Global.perDb.aUsed; +static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, + sqlite3 *pDb, int allocIfNeeded){ + S3JniDb * s = S3JniGlobal.perDb.aUsed; if(!jDb){ if(pDb){ assert(!allocIfNeeded); @@ -1174,21 +1214,21 @@ static PerDbStateJni * PerDbStateJni_for_db(JNIEnv * const env, jobject jDb, if(s->pDb == pDb) return s; } if(allocIfNeeded){ - s = PerDbStateJni_alloc(env, pDb, jDb); + s = S3JniDb_alloc(env, pDb, jDb); } return s; } #if 0 /** - An alternative form which searches for the PerDbStateJni instance for + An alternative form which searches for the S3JniDb instance for pDb with no JNIEnv-specific info. This can be (but _should_ it be?) called from the context of a separate JNIEnv than the one mapped to in the returned object. Returns 0 if no match is found. */ FIXME_THREADING -static PerDbStateJni * PerDbStateJni_for_db2(sqlite3 *pDb){ - PerDbStateJni * s = S3Global.perDb.aUsed; +static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ + S3JniDb * s = S3JniGlobal.perDb.aUsed; for( ; pDb && s; s = s->pNext){ if(s->pDb == pDb) return s; } @@ -1198,15 +1238,15 @@ static PerDbStateJni * PerDbStateJni_for_db2(sqlite3 *pDb){ #if S3JNI_ENABLE_AUTOEXT /** - Unlink ax from S3Global.autoExt and free it. + Unlink ax from S3JniGlobal.autoExt and free it. */ static void S3JniAutoExtension_free(JNIEnv * const env, S3JniAutoExtension * const ax){ if( ax ){ if( ax->pNext ) ax->pNext->pPrev = ax->pPrev; - if( ax == S3Global.autoExt.pHead ){ + if( ax == S3JniGlobal.autoExt.pHead ){ assert( !ax->pNext ); - S3Global.autoExt.pHead = ax->pNext; + S3JniGlobal.autoExt.pHead = ax->pNext; }else if( ax->pPrev ){ ax->pPrev->pNext = ax->pNext; } @@ -1217,7 +1257,7 @@ static void S3JniAutoExtension_free(JNIEnv * const env, } /** - Allocates a new auto extension and plugs it in to S3Global.autoExt. + Allocates a new auto extension and plugs it in to S3JniGlobal.autoExt. Returns 0 on OOM or if there is an error collecting the required state from jAutoExt (which must be an AutoExtension object). */ @@ -1228,7 +1268,6 @@ static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, jclass klazz; memset(ax, 0, sizeof(*ax)); klazz = (*env)->GetObjectClass(env, jAutoExt); - EXCEPTION_IS_FATAL("Cannot get class of jAutoExt."); if(!klazz){ S3JniAutoExtension_free(env, ax); return 0; @@ -1241,9 +1280,9 @@ static S3JniAutoExtension * S3JniAutoExtension_alloc(JNIEnv *const env, return 0; } ax->jObj = REF_G(jAutoExt); - ax->pNext = S3Global.autoExt.pHead; + ax->pNext = S3JniGlobal.autoExt.pHead; if( ax->pNext ) ax->pNext->pPrev = ax; - S3Global.autoExt.pHead = ax; + S3JniGlobal.autoExt.pHead = ax; } return ax; } @@ -1273,14 +1312,14 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, jfieldID member; void * pAgg; int rc = 0; - struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, S3ClassNames.sqlite3_context); - if(cacheLine && cacheLine->klazz && cacheLine->fidSetAgg){ - member = cacheLine->fidSetAgg; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, S3JniClassNames.sqlite3_context); + if(pCache && pCache->klazz && pCache->fidSetAgg){ + member = pCache->fidSetAgg; assert(member); }else{ jclass const klazz = - cacheLine ? cacheLine->klazz : (*env)->GetObjectClass(env, jCx); + pCache ? pCache->klazz : (*env)->GetObjectClass(env, jCx); member = (*env)->GetFieldID(env, klazz, "aggregateContext", "J"); if( !member ){ IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; } @@ -1289,10 +1328,10 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, "Internal error: cannot find " "sqlite3_context::aggregateContext field."); } - if(cacheLine){ - assert(cacheLine->klazz); - assert(!cacheLine->fidSetAgg); - cacheLine->fidSetAgg = member; + if(pCache){ + assert(pCache->klazz); + assert(!pCache->fidSetAgg); + pCache->fidSetAgg = member; } } pAgg = sqlite3_aggregate_context(pCx, isFinal ? 0 : 4); @@ -1307,7 +1346,7 @@ static int udf_setAggregateContext(JNIEnv * env, jobject jCx, /** Common init for OutputPointer_set_Int32() and friends. zClassName must be a - pointer from S3ClassNames. jOut must be an instance of that + pointer from S3JniClassNames. jOut must be an instance of that class. Fetches the jfieldID for jOut's [value] property, which must be of the type represented by the JNI type signature zTypeSig, and stores it in pFieldId. Fails fatally if the property is not found, @@ -1321,18 +1360,18 @@ static void setupOutputPointer(JNIEnv * const env, const char *zClassName, const char * const zTypeSig, jobject const jOut, jfieldID * const pFieldId){ jfieldID setter = 0; - struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, zClassName); - if(cacheLine && cacheLine->klazz && cacheLine->fidValue){ - setter = cacheLine->fidValue; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->klazz && pCache->fidValue){ + setter = pCache->fidValue; }else{ const jclass klazz = (*env)->GetObjectClass(env, jOut); //MARKER(("%s => %s\n", zClassName, zTypeSig)); setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); - if(cacheLine){ - assert(!cacheLine->fidValue); - cacheLine->fidValue = setter; + if(pCache){ + assert(!pCache->fidValue); + pCache->fidValue = setter; } } *pFieldId = setter; @@ -1342,7 +1381,7 @@ static void setupOutputPointer(JNIEnv * const env, const char *zClassName, to v. */ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int v){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_Int32, "I", jOut, &setter); + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int32, "I", jOut, &setter); (*env)->SetIntField(env, jOut, setter, (jint)v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } @@ -1350,7 +1389,7 @@ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, jobject jDb){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_sqlite3, + setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3, "Lorg/sqlite/jni/sqlite3;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, jDb); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3.value"); @@ -1359,7 +1398,7 @@ static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOut, jobject jStmt){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_sqlite3_stmt, + setupOutputPointer(env, S3JniClassNames.OutputPointer_sqlite3_stmt, "Lorg/sqlite/jni/sqlite3_stmt;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, jStmt); EXCEPTION_IS_FATAL("Cannot set OutputPointer.sqlite3_stmt.value"); @@ -1370,7 +1409,7 @@ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOu to v. */ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_Int64, "J", jOut, &setter); + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); (*env)->SetLongField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); } @@ -1380,7 +1419,7 @@ static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlon static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, jbyteArray const v){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_ByteArray, "[B", + setupOutputPointer(env, S3JniClassNames.OutputPointer_ByteArray, "[B", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.ByteArray.value"); @@ -1391,7 +1430,7 @@ static void OutputPointer_set_ByteArray(JNIEnv * const env, jobject const jOut, static void OutputPointer_set_String(JNIEnv * const env, jobject const jOut, jstring const v){ jfieldID setter = 0; - setupOutputPointer(env, S3ClassNames.OutputPointer_String, + setupOutputPointer(env, S3JniClassNames.OutputPointer_String, "Ljava/lang/String;", jOut, &setter); (*env)->SetObjectField(env, jOut, setter, v); EXCEPTION_IS_FATAL("Cannot set OutputPointer.String.value"); @@ -1410,7 +1449,7 @@ static int encodingTypeIsValid(int eTextRep){ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, int nRhs, const void *rhs){ - PerDbStateJni * const ps = pArg; + S3JniDb * const ps = pArg; JNIEnv * env = ps->env; jint rc = 0; jbyteArray jbaLhs = (*env)->NewByteArray(env, (jint)nLhs); @@ -1433,8 +1472,8 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, /* Collation finalizer for use by the sqlite3 internals. */ static void CollationState_xDestroy(void *pArg){ - PerDbStateJni * const ps = pArg; - JniHookState_unref( ps->env, &ps->collation, 1 ); + S3JniDb * const ps = pArg; + S3JniHookState_unref( ps->env, &ps->collation, 1 ); } /* State for sqlite3_result_java_object() and @@ -1512,35 +1551,36 @@ static void ResultJavaVal_finalizer(void *v){ /** Returns a new Java instance of the class named by zClassName, which MUST be interface-compatible with NativePointerHolder and MUST have - a no-arg constructor. Its NativePointerHolder_set() method is passed - pNative. Hypothetically returns NULL if Java fails to allocate, but - the JNI docs are not entirely clear on that detail. + a no-arg constructor. The NativePointerHolder_set() method is + passed the new Java object and pNative. Hypothetically returns NULL + if Java fails to allocate, but the JNI docs are not entirely clear + on that detail. - Always use a string literal for the 2nd argument so that we can use - its address as a cache key. + Always use a static string pointer from S3JniClassNames for the 2nd + argument so that we can use its address as a cache key. */ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zClassName, const void * pNative){ jobject rv = 0; jclass klazz = 0; jmethodID ctor = 0; - struct NphCacheLine * const cacheLine = - S3Global_nph_cache(env, zClassName); - if(cacheLine && cacheLine->midCtor){ - assert( cacheLine->klazz ); - klazz = cacheLine->klazz; - ctor = cacheLine->midCtor; + S3JniNphCache * const pCache = + S3JniGlobal_nph_cache(env, zClassName); + if(pCache && pCache->midCtor){ + assert( pCache->klazz ); + klazz = pCache->klazz; + ctor = pCache->midCtor; }else{ - klazz = cacheLine - ? cacheLine->klazz + klazz = pCache + ? pCache->klazz : (*env)->FindClass(env, zClassName); ctor = klazz ? (*env)->GetMethodID(env, klazz, "", "()V") : 0; EXCEPTION_IS_FATAL("Cannot find constructor for class."); - if(cacheLine){ - assert(zClassName == cacheLine->zClassName); - assert(cacheLine->klazz); - assert(!cacheLine->midCtor); - cacheLine->midCtor = ctor; + if(pCache){ + assert(zClassName == pCache->zClassName); + assert(pCache->klazz); + assert(!pCache->midCtor); + pCache->midCtor = ctor; } } assert(klazz); @@ -1552,16 +1592,16 @@ static jobject new_NativePointerHolder_object(JNIEnv * const env, const char *zC } static inline jobject new_sqlite3_wrapper(JNIEnv * const env, sqlite3 *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.sqlite3, sv); + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3, sv); } static inline jobject new_sqlite3_context_wrapper(JNIEnv * const env, sqlite3_context *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_context, sv); + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_context, sv); } static inline jobject new_sqlite3_stmt_wrapper(JNIEnv * const env, sqlite3_stmt *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_stmt, sv); + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_stmt, sv); } static inline jobject new_sqlite3_value_wrapper(JNIEnv * const env, sqlite3_value *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.sqlite3_value, sv); + return new_NativePointerHolder_object(env, S3JniClassNames.sqlite3_value, sv); } enum UDFType { @@ -1580,8 +1620,8 @@ typedef void (*udf_xFinal_f)(sqlite3_context*); /** State for binding Java-side UDFs. */ -typedef struct UDFState UDFState; -struct UDFState { +typedef struct S3JniUdf S3JniUdf; +struct S3JniUdf { JNIEnv * env; /* env registered from */; jobject jObj /* SQLFunction instance */; jclass klazz /* jObj's class */; @@ -1595,14 +1635,14 @@ struct UDFState { jmethodID jmidxInverse; }; -static UDFState * UDFState_alloc(JNIEnv * const env, jobject jObj){ - UDFState * const s = sqlite3_malloc(sizeof(UDFState)); +static S3JniUdf * S3JniUdf_alloc(JNIEnv * const env, jobject jObj){ + S3JniUdf * const s = sqlite3_malloc(sizeof(S3JniUdf)); if(s){ const char * zFSI = /* signature for xFunc, xStep, xInverse */ "(Lorg/sqlite/jni/sqlite3_context;[Lorg/sqlite/jni/sqlite3_value;)V"; const char * zFV = /* signature for xFinal, xValue */ "(Lorg/sqlite/jni/sqlite3_context;)V"; - memset(s, 0, sizeof(UDFState)); + memset(s, 0, sizeof(S3JniUdf)); s->env = env; s->jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); @@ -1625,7 +1665,7 @@ static UDFState * UDFState_alloc(JNIEnv * const env, jobject jObj){ return s; } -static void UDFState_free(UDFState * s){ +static void S3JniUdf_free(S3JniUdf * s){ JNIEnv * const env = s->env; if(env){ //MARKER(("UDF cleanup: %s\n", s->zFuncName)); @@ -1637,9 +1677,9 @@ static void UDFState_free(UDFState * s){ sqlite3_free(s); } -static void UDFState_finalizer(void * s){ +static void S3JniUdf_finalizer(void * s){ //MARKER(("UDF finalizer @ %p\n", s)); - if(s) UDFState_free((UDFState*)s); + if(s) S3JniUdf_free((S3JniUdf*)s); } /** @@ -1671,7 +1711,7 @@ static int udf_args(JNIEnv *env, *jArgv = 0; if(!jcx) goto error_oom; ja = (*env)->NewObjectArray(env, argc, - S3Global_env_cache(env)->g.cObj, + S3JniGlobal_env_cache(env)->g.cObj, NULL); if(!ja) goto error_oom; for(i = 0; i < argc; ++i){ @@ -1715,7 +1755,7 @@ static int udf_report_exception(sqlite3_context * cx, */ static int udf_xFSI(sqlite3_context* pCx, int argc, sqlite3_value** argv, - UDFState * s, + S3JniUdf * s, jmethodID xMethodID, const char * zFuncType){ JNIEnv * const env = s->env; @@ -1742,7 +1782,7 @@ static int udf_xFSI(sqlite3_context* pCx, int argc, Sets up the state for calling a Java-side xFinal/xValue() UDF, calls it, and returns 0 on success. */ -static int udf_xFV(sqlite3_context* cx, UDFState * s, +static int udf_xFV(sqlite3_context* cx, S3JniUdf * s, jmethodID xMethodID, const char *zFuncType){ JNIEnv * const env = s->env; @@ -1769,30 +1809,30 @@ static int udf_xFV(sqlite3_context* cx, UDFState * s, static void udf_xFunc(sqlite3_context* cx, int argc, sqlite3_value** argv){ - UDFState * const s = (UDFState*)sqlite3_user_data(cx); - ++S3Global.metrics.udf.nFunc; + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nFunc; udf_xFSI(cx, argc, argv, s, s->jmidxFunc, "xFunc"); } static void udf_xStep(sqlite3_context* cx, int argc, sqlite3_value** argv){ - UDFState * const s = (UDFState*)sqlite3_user_data(cx); - ++S3Global.metrics.udf.nStep; + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nStep; udf_xFSI(cx, argc, argv, s, s->jmidxStep, "xStep"); } static void udf_xFinal(sqlite3_context* cx){ - UDFState * const s = (UDFState*)sqlite3_user_data(cx); - ++S3Global.metrics.udf.nFinal; + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nFinal; udf_xFV(cx, s, s->jmidxFinal, "xFinal"); } static void udf_xValue(sqlite3_context* cx){ - UDFState * const s = (UDFState*)sqlite3_user_data(cx); - ++S3Global.metrics.udf.nValue; + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nValue; udf_xFV(cx, s, s->jmidxValue, "xValue"); } static void udf_xInverse(sqlite3_context* cx, int argc, sqlite3_value** argv){ - UDFState * const s = (UDFState*)sqlite3_user_data(cx); - ++S3Global.metrics.udf.nInverse; + S3JniUdf * const s = (S3JniUdf*)sqlite3_user_data(cx); + ++S3JniGlobal.metrics.udf.nInverse; udf_xFSI(cx, argc, argv, s, s->jmidxInverse, "xInverse"); } @@ -1802,8 +1842,6 @@ static void udf_xInverse(sqlite3_context* cx, int argc, // except for this macro-generated subset which are kept together here // at the front... //////////////////////////////////////////////////////////////////////// -WRAP_INT_DB(1error_1offset, sqlite3_error_offset) -WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) WRAP_INT_STMT(1bind_1parameter_1count, sqlite3_bind_parameter_count) WRAP_INT_DB(1changes, sqlite3_changes) WRAP_INT64_DB(1changes64, sqlite3_changes64) @@ -1818,6 +1856,8 @@ WRAP_STR_STMT_INT(1column_1origin_1name, sqlite3_column_origin_name) WRAP_STR_STMT_INT(1column_1table_1name, sqlite3_column_table_name) WRAP_INT_STMT_INT(1column_1type, sqlite3_column_type) WRAP_INT_STMT(1data_1count, sqlite3_data_count) +WRAP_INT_DB(1error_1offset, sqlite3_error_offset) +WRAP_INT_DB(1extended_1errcode, sqlite3_extended_errcode) WRAP_MUTF8_VOID(1libversion, sqlite3_libversion) WRAP_INT_VOID(1libversion_1number, sqlite3_libversion_number) WRAP_INT_INT(1sleep, sqlite3_sleep) @@ -1838,23 +1878,23 @@ WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) /* Central auto-extension handler. */ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ - S3JniAutoExtension const * pAX = S3Global.autoExt.pHead; + S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; int rc; JNIEnv * env = 0; - PerDbStateJni * const ps = S3Global.autoExt.psOpening; + S3JniDb * const ps = S3JniGlobal.autoExt.psOpening; - S3Global.autoExt.psOpening = 0; + S3JniGlobal.autoExt.psOpening = 0; if( !pAX ){ - assert( 0==S3Global.autoExt.isRunning ); + assert( 0==S3JniGlobal.autoExt.isRunning ); return 0; - }else if( S3Global.autoExt.isRunning ){ + }else if( S3JniGlobal.autoExt.isRunning ){ *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " "auto-extensions are running."); return SQLITE_MISUSE; }else if(!ps){ - MARKER(("Internal error: cannot find PerDbStateJni for auto-extension\n")); + MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); return SQLITE_ERROR; - }else if( (*S3Global.jvm)->GetEnv(S3Global.jvm, (void **)&env, JNI_VERSION_1_8) ){ + }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ assert(!"Cannot get JNIEnv"); *pzErr = sqlite3_mprintf("Could not get current JNIEnv."); return SQLITE_ERROR; @@ -1862,8 +1902,8 @@ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, assert( !ps->pDb /* it's still being opened */ ); ps->pDb = pDb; assert( ps->jDb ); - NativePointerHolder_set(env, ps->jDb, pDb, S3ClassNames.sqlite3); - S3Global.autoExt.isRunning = 1; + NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); + S3JniGlobal.autoExt.isRunning = 1; for( ; pAX; pAX = pAX->pNext ){ rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); IFTHREW { @@ -1880,7 +1920,7 @@ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, break; } } - S3Global.autoExt.isRunning = 0; + S3JniGlobal.autoExt.isRunning = 0; return rc; } @@ -1892,7 +1932,7 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ else if( 0==once && ++once ){ sqlite3_auto_extension( (void(*)(void))s3jni_auto_extension ); } - ax = S3Global.autoExt.pHead; + ax = S3JniGlobal.autoExt.pHead; for( ; ax; ax = ax->pNext ){ if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ return 0 /* C API treats this as a no-op. */; @@ -1971,7 +2011,7 @@ JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, } static int s3jni_busy_handler(void* pState, int n){ - PerDbStateJni * const ps = (PerDbStateJni *)pState; + S3JniDb * const ps = (S3JniDb *)pState; int rc = 0; if( ps->busyHandler.jObj ){ JNIEnv * const env = ps->env; @@ -1987,28 +2027,28 @@ static int s3jni_busy_handler(void* pState, int n){ } JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); int rc = 0; if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ - JniHookState * const pHook = &ps->busyHandler; + S3JniHookState * const pHook = &ps->busyHandler; if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ /* Same object - this is a no-op. */ return 0; } - JniHookState_unref(env, pHook, 1); + S3JniHookState_unref(env, pHook, 1); pHook->jObj = REF_G(jBusy); pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); IFTHREW { - JniHookState_unref(env, pHook, 0); + S3JniHookState_unref(env, pHook, 0); rc = SQLITE_ERROR; } if(rc){ return rc; } }else{ - JniHookState_unref(env, &ps->busyHandler, 1); + S3JniHookState_unref(env, &ps->busyHandler, 1); } return jBusy ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) @@ -2016,9 +2056,9 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ } JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); if( ps ){ - JniHookState_unref(env, &ps->busyHandler, 1); + S3JniHookState_unref(env, &ps->busyHandler, 1); return sqlite3_busy_timeout(ps->pDb, (int)ms); } return SQLITE_MISUSE; @@ -2027,8 +2067,8 @@ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ #if S3JNI_ENABLE_AUTOEXT JDECL(jboolean,1cancel_1auto_1extension)(JENV_OSELF, jobject jAutoExt){ S3JniAutoExtension * ax;; - if( S3Global.autoExt.isRunning ) return JNI_FALSE; - for( ax = S3Global.autoExt.pHead; ax; ax = ax->pNext ){ + if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; + for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ if( (*env)->IsSameObject(env, ax->jObj, jAutoExt) ){ S3JniAutoExtension_free(env, ax); return JNI_TRUE; @@ -2044,14 +2084,14 @@ JDECL(jboolean,1cancel_1auto_1extension)(JENV_OSELF, jobject jAutoExt){ */ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ int rc = 0; - PerDbStateJni * ps = 0; + S3JniDb * ps = 0; assert(version == 1 || version == 2); - ps = PerDbStateJni_for_db(env, jDb, 0, 0); + ps = S3JniDb_for_db(env, jDb, 0, 0); if(ps){ rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); - PerDbStateJni_set_aside(ps) + S3JniDb_set_aside(ps) /* MUST come after close() because of ps->trace. */; - NativePointerHolder_set(env, jDb, 0, S3ClassNames.sqlite3); + NativePointerHolder_set(env, jDb, 0, S3JniClassNames.sqlite3); } return (jint)rc; } @@ -2080,7 +2120,7 @@ static unsigned int s3jni_utf16_strlen(void const * z){ */ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, int eTextRep, const void * z16Name){ - PerDbStateJni * const ps = pState; + S3JniDb * const ps = pState; JNIEnv * const env = ps->env; unsigned int const nName = s3jni_utf16_strlen(z16Name); jstring jName = (*env)->NewString(env, (jchar const *)z16Name, nName); @@ -2092,20 +2132,18 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, ps->collationNeeded.midCallback, ps->jDb, (jint)eTextRep, jName); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("collation-needed callback"); - EXCEPTION_CLEAR; - s3jni_db_error(ps->pDb, SQLITE_ERROR, "collation-needed hook threw."); + s3jni_db_exception(env, ps, 0, "collation-needed callback"); } } UNREF_L(jName); } JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; - JniHookState * const pHook = &ps->collationNeeded; + S3JniHookState * const pHook = &ps->collationNeeded; int rc; if( !ps ) return SQLITE_MISUSE; @@ -2116,7 +2154,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ } if( !jHook ){ UNREF_G(pOld); - memset(pHook, 0, sizeof(JniHookState)); + memset(pHook, 0, sizeof(S3JniHookState)); sqlite3_collation_needed(ps->pDb, 0, 0); return 0; } @@ -2124,10 +2162,9 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ xCallback = (*env)->GetMethodID(env, klazz, "xCollationNeeded", "(Lorg/sqlite/jni/sqlite3;ILjava/lang/String;)I"); IFTHREW { - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_MISUSE, - "Cannot not find matching callback on " - "collation-needed hook object."); + rc = s3jni_db_exception(env, ps, SQLITE_MISUSE, + "Cannot not find matching callback on " + "collation-needed hook object."); }else{ pHook->midCallback = xCallback; pHook->jObj = REF_G(jHook); @@ -2188,7 +2225,7 @@ JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, } -static int s3jni_commit_rollback_hook_impl(int isCommit, PerDbStateJni * const ps){ +static int s3jni_commit_rollback_hook_impl(int isCommit, S3JniDb * const ps){ JNIEnv * const env = ps->env; int rc = isCommit ? (int)(*env)->CallIntMethod(env, ps->commitHook.jObj, @@ -2212,11 +2249,11 @@ static void s3jni_rollback_hook_impl(void *pP){ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, jobject jHook){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; - JniHookState * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; + S3JniHookState * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; if(!ps){ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; @@ -2232,7 +2269,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobje UNREF_G(pOld); pOld = tmp; } - memset(pHook, 0, sizeof(JniHookState)); + memset(pHook, 0, sizeof(S3JniHookState)); if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); else sqlite3_rollback_hook(ps->pDb, 0, 0); return pOld; @@ -2282,13 +2319,13 @@ JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( JENV_CSELF, jobject jDb, jint op, jstring jStr ){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); int rc; char *zStr; switch( (ps && jStr) ? op : 0 ){ case SQLITE_DBCONFIG_MAINDBNAME: - zStr = s3jni_jstring_to_utf8(S3Global_env_cache(env), jStr, 0); + zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); if( zStr ){ rc = sqlite3_db_config(ps->pDb, (int)op, zStr); if( rc ){ @@ -2311,7 +2348,7 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2)( JENV_CSELF, jobject jDb, jint op, jobject jOut ){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); int rc; switch( ps ? op : 0 ){ @@ -2348,18 +2385,18 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_ JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); - PerDbStateJni * const ps = pDb ? PerDbStateJni_for_db(env, 0, pDb, 0) : 0; + S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; return ps ? ps->jDb : 0; } JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, jstring name, jint eTextRep, jobject oCollation){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; int rc; const char *zName; - JniHookState * pHook; + S3JniHookState * pHook; if(!ps) return (jint)SQLITE_NOMEM; pHook = &ps->collation; klazz = (*env)->GetObjectClass(env, oCollation); @@ -2379,14 +2416,14 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, pHook->jObj = REF_G(oCollation); pHook->klazz = REF_G(klazz); }else{ - JniHookState_unref(env, pHook, 1); + S3JniHookState_unref(env, pHook, 1); } return (jint)rc; } static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, jint nArg, jint eTextRep, jobject jFunctor){ - UDFState * s = 0; + S3JniUdf * s = 0; int rc; sqlite3 * const pDb = PtrGet_sqlite3(jDb); const char * zFuncName = 0; @@ -2395,24 +2432,24 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, return s3jni_db_error(pDb, SQLITE_FORMAT, "Invalid function encoding option."); } - s = UDFState_alloc(env, jFunctor); + s = S3JniUdf_alloc(env, jFunctor); if( !s ) return SQLITE_NOMEM; else if( UDF_UNKNOWN_TYPE==s->type ){ rc = s3jni_db_error(pDb, SQLITE_MISUSE, "Cannot unambiguously determine function type."); - UDFState_free(s); + S3JniUdf_free(s); goto error_cleanup; } zFuncName = JSTR_TOC(jFuncName); if(!zFuncName){ rc = SQLITE_NOMEM; - UDFState_free(s); + S3JniUdf_free(s); goto error_cleanup; } if( UDF_WINDOW == s->type ){ rc = sqlite3_create_window_function(pDb, zFuncName, nArg, eTextRep, s, udf_xStep, udf_xFinal, udf_xValue, - udf_xInverse, UDFState_finalizer); + udf_xInverse, S3JniUdf_finalizer); }else{ udf_xFunc_f xFunc = 0; udf_xStep_f xStep = 0; @@ -2425,7 +2462,7 @@ static jint create_function(JNIEnv * env, jobject jDb, jstring jFuncName, xFinal = udf_xFinal; } rc = sqlite3_create_function_v2(pDb, zFuncName, nArg, eTextRep, s, - xFunc, xStep, xFinal, UDFState_finalizer); + xFunc, xStep, xFinal, S3JniUdf_finalizer); } if( 0==rc ){ s->zFuncName = sqlite3_mprintf("%s", zFuncName) @@ -2445,8 +2482,8 @@ JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, } JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); char *zDbName; jstring jRv = 0; int nStr = 0; @@ -2472,7 +2509,7 @@ JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ JDECL(jstring,1errmsg)(JENV_CSELF, jobject jpDb){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); - JNIEnvCache * const jc = pDb ? S3Global_env_cache(env) : 0; + S3JniEnvCache * const jc = pDb ? S3JniGlobal_env_cache(env) : 0; return jc ? s3jni_utf8_to_jstring(jc, sqlite3_errmsg(pDb), -1) : 0; } @@ -2501,7 +2538,7 @@ JDECL(jint,1initialize)(JENV_CSELF){ is needed for certain tracing flags. At a minumum those ops are: step, reset, finalize, prepare. */ -static jobject stmt_set_current(JNIEnvCache * const jc, jobject jStmt){ +static jobject stmt_set_current(S3JniEnvCache * const jc, jobject jStmt){ jobject const old = jc->currentStmt; jc->currentStmt = jStmt; return old; @@ -2511,10 +2548,10 @@ JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_finalize(pStmt); - NativePointerHolder_set(env, jpStmt, 0, S3ClassNames.sqlite3_stmt); + NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pPrev); } return rc; @@ -2526,10 +2563,10 @@ JDECL(jlong,1last_1insert_1rowid)(JENV_CSELF, jobject jpDb){ } //! Pre-open() code common to sqlite3_open(_v2)(). -static int s3jni_open_pre(JNIEnv * const env, JNIEnvCache **jc, +static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, jstring jDbName, char **zDbName, - PerDbStateJni ** ps, jobject *jDb){ - *jc = S3Global_env_cache(env); + S3JniDb ** ps, jobject *jDb){ + *jc = S3JniGlobal_env_cache(env); if(!*jc) return SQLITE_NOMEM; *zDbName = jDbName ? s3jni_jstring_to_utf8(*jc, jDbName, 0) : 0; if(jDbName && !*zDbName) return SQLITE_NOMEM; @@ -2539,11 +2576,11 @@ static int s3jni_open_pre(JNIEnv * const env, JNIEnvCache **jc, *zDbName = 0; return SQLITE_NOMEM; } - *ps = PerDbStateJni_alloc(env, 0, *jDb); + *ps = S3JniDb_alloc(env, 0, *jDb); #if S3JNI_ENABLE_AUTOEXT if(*ps){ - assert(!S3Global.autoExt.psOpening); - S3Global.autoExt.psOpening = *ps; + assert(!S3JniGlobal.autoExt.psOpening); + S3JniGlobal.autoExt.psOpening = *ps; } #endif return *ps ? 0 : SQLITE_NOMEM; @@ -2560,21 +2597,21 @@ static int s3jni_open_pre(JNIEnv * const env, JNIEnvCache **jc, Returns theRc. */ -static int s3jni_open_post(JNIEnv * const env, PerDbStateJni * ps, +static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, sqlite3 **ppDb, jobject jOut, int theRc){ #if S3JNI_ENABLE_AUTOEXT - assert( S3Global.autoExt.pHead ? 0==S3Global.autoExt.psOpening : 1 ); - S3Global.autoExt.psOpening = 0; + assert( S3JniGlobal.autoExt.pHead ? 0==S3JniGlobal.autoExt.psOpening : 1 ); + S3JniGlobal.autoExt.psOpening = 0; #endif if(*ppDb){ assert(ps->jDb); #if S3JNI_ENABLE_AUTOEXT - assert( S3Global.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); + assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); #endif ps->pDb = *ppDb; - NativePointerHolder_set(env, ps->jDb, *ppDb, S3ClassNames.sqlite3); + NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); }else{ - PerDbStateJni_set_aside(ps); + S3JniDb_set_aside(ps); ps = 0; } OutputPointer_set_sqlite3(env, jOut, ps ? ps->jDb : 0); @@ -2585,8 +2622,8 @@ JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ sqlite3 * pOut = 0; char *zName = 0; jobject jDb = 0; - PerDbStateJni * ps = 0; - JNIEnvCache * jc = 0; + S3JniDb * ps = 0; + S3JniEnvCache * jc = 0; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( rc ) return rc; rc = sqlite3_open(zName, &pOut); @@ -2602,8 +2639,8 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, sqlite3 * pOut = 0; char *zName = 0; jobject jDb = 0; - PerDbStateJni * ps = 0; - JNIEnvCache * jc = 0; + S3JniDb * ps = 0; + S3JniEnvCache * jc = 0; char *zVfs = 0; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc && strVfs ){ @@ -2633,7 +2670,7 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass jobject jStmt = 0; const char * zTail = 0; jbyte * const pBuf = JBA_TOC(baSql); - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jobject const pOldStmt = stmt_set_current(jc, 0); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); @@ -2669,13 +2706,13 @@ end: assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); } - NativePointerHolder_set(env, jStmt, pStmt, S3ClassNames.sqlite3_stmt); + NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); }else{ UNREF_L(jStmt); jStmt = 0; } OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); - //NativePointerHolder_set(env, jOutStmt, pStmt, S3ClassNames.sqlite3_stmt); + //NativePointerHolder_set(env, jOutStmt, pStmt, S3JniClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } @@ -2697,21 +2734,19 @@ JDECL(jint,1prepare_1v3)(JNIEnv * const env, jclass self, jobject jDb, jbyteArra static int s3jni_progress_handler_impl(void *pP){ - PerDbStateJni * const ps = (PerDbStateJni *)pP; + S3JniDb * const ps = (S3JniDb *)pP; JNIEnv * const env = ps->env; int rc = (int)(*env)->CallIntMethod(env, ps->progress.jObj, ps->progress.midCallback); IFTHREW{ - EXCEPTION_WARN_CALLBACK_THREW("sqlite3_progress_handler() callback"); - EXCEPTION_CLEAR; - rc = s3jni_db_error(ps->pDb, SQLITE_ERROR, - "sqlite3_progress_handler() callback threw."); + rc = s3jni_db_exception(env, ps, rc, + "sqlite3_progress_handler() callback threw"); } return rc; } JDECL(void,1progress_1handler)(JENV_CSELF,jobject jDb, jint n, jobject jProgress){ - PerDbStateJni * ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; jmethodID xCallback; if( n<1 || !jProgress ){ @@ -2746,7 +2781,7 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_reset(pStmt); (void)stmt_set_current(jc, pPrev); @@ -2756,9 +2791,9 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ #if S3JNI_ENABLE_AUTOEXT JDECL(void,1reset_1auto_1extension)(JENV_CSELF){ - if(!S3Global.autoExt.isRunning){ - while( S3Global.autoExt.pHead ){ - S3JniAutoExtension_free(env, S3Global.autoExt.pHead); + if(!S3JniGlobal.autoExt.isRunning){ + while( S3JniGlobal.autoExt.pHead ){ + S3JniAutoExtension_free(env, S3JniGlobal.autoExt.pHead); } } } @@ -2946,13 +2981,13 @@ JDECL(jobject,1rollback_1hook)(JENV_CSELF,jobject jDb, jobject jHook){ /* sqlite3_set_authorizer() callback proxy. */ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, const char*z2,const char*z3){ - PerDbStateJni * const ps = pState; + S3JniDb * const ps = pState; JNIEnv * const env = ps->env; jstring const s0 = z0 ? (*env)->NewStringUTF(env, z0) : 0; jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; - JniHookState const * const pHook = &ps->authHook; + S3JniHookState const * const pHook = &ps->authHook; int rc; assert( pHook->jObj ); @@ -2970,12 +3005,12 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, } JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); - JniHookState * const pHook = ps ? &ps->authHook : 0; + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + S3JniHookState * const pHook = ps ? &ps->authHook : 0; if( !ps ) return SQLITE_MISUSE; else if( !jHook ){ - JniHookState_unref(env, pHook, 0); + S3JniHookState_unref(env, pHook, 0); return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); }else{ int rc = 0; @@ -2984,7 +3019,7 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ /* Same object - this is a no-op. */ return 0; } - JniHookState_unref(env, pHook, 0); + S3JniHookState_unref(env, pHook, 0); } pHook->jObj = REF_G(jHook); pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); @@ -2997,12 +3032,12 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ "Ljava/lang/String;" ")I"); IFTHREW { - JniHookState_unref(env, pHook, 0); + S3JniHookState_unref(env, pHook, 0); return s3jni_db_error(ps->pDb, SQLITE_ERROR, "Error setting up Java parts of authorizer hook."); } rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); - if( rc ) JniHookState_unref(env, pHook, 0); + if( rc ) S3JniHookState_unref(env, pHook, 0); return rc; } } @@ -3014,8 +3049,8 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ } JDECL(jint,1shutdown)(JENV_CSELF){ - S3Global_JNIEnvCache_clear(); - /* Do not clear S3Global.jvm: it's legal to call + S3JniGlobal_S3JniEnvCache_clear(); + /* Do not clear S3JniGlobal.jvm: it's legal to call sqlite3_initialize() again to restart the lib. */ return sqlite3_shutdown(); } @@ -3024,7 +3059,7 @@ JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); if(pStmt){ - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jobject const jPrevStmt = stmt_set_current(jc, jStmt); rc = sqlite3_step(pStmt); (void)stmt_set_current(jc, jPrevStmt); @@ -3033,12 +3068,12 @@ JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ } static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ - PerDbStateJni * const ps = (PerDbStateJni *)pC; + S3JniDb * const ps = (S3JniDb *)pC; JNIEnv * const env = ps->env; jobject jX = NULL /* the tracer's X arg */; jobject jP = NULL /* the tracer's P arg */; jobject jPUnref = NULL /* potentially a local ref to jP */; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); int rc; switch(traceflag){ case SQLITE_TRACE_STMT: @@ -3088,7 +3123,7 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ } JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; if( !traceMask || !jTracer ){ if(ps){ @@ -3112,7 +3147,7 @@ JDECL(jint,1trace_1v2)(JENV_CSELF,jobject jDb, jint traceMask, jobject jTracer){ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, const char *zTable, sqlite3_int64 nRowid){ - PerDbStateJni * const ps = pState; + S3JniDb * const ps = pState; JNIEnv * const env = ps->env; /* ACHTUNG: this will break if zDb or zTable contain chars which are different in MUTF-8 than UTF-8. That seems like a low risk, @@ -3139,11 +3174,11 @@ static void s3jni_update_hook_impl(void * pState, int opId, const char *zDb, JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; jobject pOld = 0; jmethodID xCallback; - JniHookState * const pHook = &ps->updateHook; + S3JniHookState * const pHook = &ps->updateHook; if(!ps){ s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); return 0; @@ -3159,7 +3194,7 @@ JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ UNREF_G(pOld); pOld = tmp; } - memset(pHook, 0, sizeof(JniHookState)); + memset(pHook, 0, sizeof(S3JniHookState)); sqlite3_update_hook(ps->pDb, 0, 0); return pOld; } @@ -3292,28 +3327,28 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ puts("sizeofs:"); #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); - SO(JNIEnvCache); - SO(JniHookState); - SO(PerDbStateJni); - SO(S3ClassNames); + SO(S3JniEnvCache); + SO(S3JniHookState); + SO(S3JniDb); + SO(S3JniClassNames); printf("\t(^^^ %u NativePointerHolder subclasses)\n", - (unsigned)(sizeof(S3ClassNames) / sizeof(const char *))); - SO(S3Global); + (unsigned)(sizeof(S3JniClassNames) / sizeof(const char *))); + SO(S3JniGlobal); SO(S3JniAutoExtension); - SO(UDFState); + SO(S3JniUdf); printf("Cache info:\n"); printf("\tNativePointerHolder cache: %u misses, %u hits\n", - S3Global.metrics.nphCacheMisses, - S3Global.metrics.nphCacheHits); + S3JniGlobal.metrics.nphCacheMisses, + S3JniGlobal.metrics.nphCacheHits); printf("\tJNIEnv cache %u misses, %u hits\n", - S3Global.metrics.envCacheMisses, - S3Global.metrics.envCacheHits); + S3JniGlobal.metrics.envCacheMisses, + S3JniGlobal.metrics.envCacheHits); puts("UDF calls:"); -#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3Global.metrics.udf.n##T) +#define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); #undef UDF printf("xDestroy calls across all callback types: %u\n", - S3Global.metrics.nDestroy); + S3JniGlobal.metrics.nDestroy); #undef SO } @@ -3340,10 +3375,10 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ JNIEXPORT ReturnType JNICALL \ JFuncNameFtsTok(Suffix) -#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.fts5_api) -#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.fts5_tokenizer) -#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.Fts5Context) -#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3ClassNames.Fts5Tokenizer) +#define PtrGet_fts5_api(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_api) +#define PtrGet_fts5_tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.fts5_tokenizer) +#define PtrGet_Fts5Context(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Context) +#define PtrGet_Fts5Tokenizer(OBJ) NativePointerHolder_get(env,OBJ,S3JniClassNames.Fts5Tokenizer) #define Fts5ExtDecl Fts5ExtensionApi const * const fext = s3jni_ftsext() /** @@ -3391,7 +3426,6 @@ static Fts5JniAux * Fts5JniAux_alloc(JNIEnv * const env, jobject jObj){ s->env = env; s->jObj = REF_G(jObj); s->klazz = REF_G((*env)->GetObjectClass(env, jObj)); - EXCEPTION_IS_FATAL("Cannot get class for FTS5 aux function object."); s->jmid = (*env)->GetMethodID(env, s->klazz, "xFunction", zSig); IFTHREW{ EXCEPTION_REPORT; @@ -3408,10 +3442,10 @@ static inline Fts5ExtensionApi const * s3jni_ftsext(void){ } static inline jobject new_Fts5Context_wrapper(JNIEnv * const env, Fts5Context *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.Fts5Context, sv); + return new_NativePointerHolder_object(env, S3JniClassNames.Fts5Context, sv); } static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ - return new_NativePointerHolder_object(env, S3ClassNames.fts5_api, sv); + return new_NativePointerHolder_object(env, S3JniClassNames.fts5_api, sv); } /** @@ -3419,9 +3453,9 @@ static inline jobject new_fts5_api_wrapper(JNIEnv * const env, fts5_api *sv){ instance, or NULL on OOM. */ static jobject s3jni_getFts5ExensionApi(JNIEnv * const env){ - JNIEnvCache * const row = S3Global_env_cache(env); + S3JniEnvCache * const row = S3JniGlobal_env_cache(env); if( !row->jFtsExt ){ - row->jFtsExt = new_NativePointerHolder_object(env, S3ClassNames.Fts5ExtensionApi, + row->jFtsExt = new_NativePointerHolder_object(env, S3JniClassNames.Fts5ExtensionApi, s3jni_ftsext()); if(row->jFtsExt) row->jFtsExt = REF_G(row->jFtsExt); } @@ -3445,7 +3479,7 @@ static fts5_api *s3jni_fts5_api_from_db(sqlite3 *db){ } JDECLFtsApi(jobject,getInstanceForDb)(JENV_CSELF,jobject jDb){ - PerDbStateJni * const ps = PerDbStateJni_for_db(env, jDb, 0, 0); + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jobject rv = 0; if(!ps) return 0; else if(ps->jFtsApi){ @@ -3486,7 +3520,7 @@ JDECLFtsXA(jint,xColumnText)(JENV_OSELF,jobject jCtx, jint iCol, int rc = fext->xColumnText(PtrGet_Fts5Context(jCtx), (int)iCol, &pz, &pn); if( 0==rc ){ - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jstring jstr = pz ? s3jni_utf8_to_jstring(jc, pz, pn) : 0; if( pz ){ if( jstr ){ @@ -3643,11 +3677,10 @@ JDECLFtsXA(jint,xPhraseCount)(JENV_OSELF,jobject jCtx){ /** Initializes jc->jPhraseIter if it needed it. */ -static void s3jni_phraseIter_init(JNIEnv *const env, JNIEnvCache * const jc, +static void s3jni_phraseIter_init(JNIEnv *const env, S3JniEnvCache * const jc, jobject jIter){ if(!jc->jPhraseIter.klazz){ jclass klazz = (*env)->GetObjectClass(env, jIter); - EXCEPTION_IS_FATAL("Cannot get class of Fts5PhraseIter object."); jc->jPhraseIter.klazz = REF_G(klazz); jc->jPhraseIter.fidA = (*env)->GetFieldID(env, klazz, "a", "J"); EXCEPTION_IS_FATAL("Cannot get Fts5PhraseIter.a field."); @@ -3657,7 +3690,7 @@ static void s3jni_phraseIter_init(JNIEnv *const env, JNIEnvCache * const jc, } /* Copy the 'a' and 'b' fields from pSrc to Fts5PhraseIter object jIter. */ -static void s3jni_phraseIter_NToJ(JNIEnv *const env, JNIEnvCache const * const jc, +static void s3jni_phraseIter_NToJ(JNIEnv *const env, S3JniEnvCache const * const jc, Fts5PhraseIter const * const pSrc, jobject jIter){ assert(jc->jPhraseIter.klazz); @@ -3668,7 +3701,7 @@ static void s3jni_phraseIter_NToJ(JNIEnv *const env, JNIEnvCache const * const j } /* Copy the 'a' and 'b' fields from Fts5PhraseIter object jIter to pDest. */ -static void s3jni_phraseIter_JToN(JNIEnv *const env, JNIEnvCache const * const jc, +static void s3jni_phraseIter_JToN(JNIEnv *const env, S3JniEnvCache const * const jc, jobject jIter, Fts5PhraseIter * const pDest){ assert(jc->jPhraseIter.klazz); pDest->a = @@ -3683,7 +3716,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0, iOff = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3700,7 +3733,7 @@ JDECLFtsXA(jint,xPhraseFirst)(JENV_OSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int rc, iCol = 0; s3jni_phraseIter_init(env, jc, jIter); @@ -3716,7 +3749,7 @@ JDECLFtsXA(jint,xPhraseFirstColumn)(JENV_OSELF,jobject jCtx, jint iPhrase, JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, jobject jOutCol, jobject jOutOff){ Fts5ExtDecl; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0, iOff = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3731,7 +3764,7 @@ JDECLFtsXA(void,xPhraseNext)(JENV_OSELF,jobject jCtx, jobject jIter, JDECLFtsXA(void,xPhraseNextColumn)(JENV_OSELF,jobject jCtx, jobject jIter, jobject jOutCol){ Fts5ExtDecl; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); Fts5PhraseIter iter; int iCol = 0; if(!jc->jPhraseIter.klazz) return /*SQLITE_MISUSE*/; @@ -3753,7 +3786,7 @@ JDECLFtsXA(jint,xPhraseSize)(JENV_OSELF,jobject jCtx, jint iPhrase){ struct s3jni_xQueryPhraseState { JNIEnv *env; Fts5ExtensionApi const * fext; - JNIEnvCache const * jc; + S3JniEnvCache const * jc; jmethodID midCallback; jobject jCallback; jobject jFcx; @@ -3785,13 +3818,10 @@ static int s3jni_xQueryPhrase(const Fts5ExtensionApi *xapi, JDECLFtsXA(jint,xQueryPhrase)(JENV_OSELF,jobject jFcx, jint iPhrase, jobject jCallback){ Fts5ExtDecl; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; jclass klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; - if( !klazz ){ - EXCEPTION_CLEAR; - return SQLITE_MISUSE; - } + if( !klazz ) return SQLITE_MISUSE; s.env = env; s.jc = jc; s.jCallback = jCallback; @@ -3874,17 +3904,13 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, jint tokFlags, jobject jFcx, jbyteArray jbaText, jobject jCallback){ Fts5ExtDecl; - JNIEnvCache * const jc = S3Global_env_cache(env); + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); struct s3jni_xQueryPhraseState s; int rc = 0; - jbyte * const pText = JBA_TOC(jbaText); - jsize nText = (*env)->GetArrayLength(env, jbaText); + jbyte * const pText = jCallback ? JBA_TOC(jbaText) : 0; + jsize nText = pText ? (*env)->GetArrayLength(env, jbaText) : 0; jclass const klazz = jCallback ? (*env)->GetObjectClass(env, jCallback) : NULL; - if( !klazz ){ - EXCEPTION_CLEAR; - JBA_RELEASE(jbaText, pText); - return SQLITE_MISUSE; - } + if( !klazz ) return SQLITE_MISUSE; memset(&s, 0, sizeof(s)); s.env = env; s.jc = jc; @@ -3901,11 +3927,11 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, s.tok.jba = REF_L(jbaText); s.tok.zPrev = (const char *)pText; s.tok.nPrev = (int)nText; - if( zClassName == S3ClassNames.Fts5ExtensionApi ){ + if( zClassName == S3JniClassNames.Fts5ExtensionApi ){ rc = fext->xTokenize(PtrGet_Fts5Context(jFcx), (const char *)pText, (int)nText, &s, s3jni_xTokenize_xToken); - }else if( zClassName == S3ClassNames.fts5_tokenizer ){ + }else if( zClassName == S3JniClassNames.fts5_tokenizer ){ fts5_tokenizer * const pTok = PtrGet_fts5_tokenizer(jSelf); rc = pTok->xTokenize(PtrGet_Fts5Tokenizer(jFcx), &s, tokFlags, (const char *)pText, (int)nText, @@ -3923,13 +3949,13 @@ static jint s3jni_fts5_xTokenize(JENV_OSELF, const char *zClassName, JDECLFtsXA(jint,xTokenize)(JENV_OSELF,jobject jFcx, jbyteArray jbaText, jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3ClassNames.Fts5ExtensionApi, + return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5ExtensionApi, 0, jFcx, jbaText, jCallback); } JDECLFtsTok(jint,xTokenize)(JENV_OSELF,jobject jFcx, jint tokFlags, jbyteArray jbaText, jobject jCallback){ - return s3jni_fts5_xTokenize(env, jSelf, S3ClassNames.Fts5Tokenizer, + return s3jni_fts5_xTokenize(env, jSelf, S3JniClassNames.Fts5Tokenizer, tokFlags, jFcx, jbaText, jCallback); } @@ -3947,14 +3973,14 @@ JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ //////////////////////////////////////////////////////////////////////// /** - Uncaches the current JNIEnv from the S3Global state, clearing any + Uncaches the current JNIEnv from the S3JniGlobal state, clearing any resources owned by that cache entry and making that slot available for re-use. It is important that the Java-side decl of this function be declared as synchronous. */ JNIEXPORT jboolean JNICALL Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ - return S3Global_env_uncache(env) ? JNI_TRUE : JNI_FALSE; + return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; } @@ -4012,21 +4038,20 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF, jclass klazzSjni){ {0,0} }; jfieldID fieldId; - //jclass const klazz = (*env)->GetObjectClass(env, sJni); const ConfigFlagEntry * pConfFlag; - memset(&S3Global, 0, sizeof(S3Global)); - if( (*env)->GetJavaVM(env, &S3Global.jvm) ){ + memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); + if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); return; } - (void)S3Global_env_cache(env); - if( !S3Global.envCache.aHead ){ + (void)S3JniGlobal_env_cache(env); + if( !S3JniGlobal.envCache.aHead ){ (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); return; } - assert( 1 == S3Global.metrics.envCacheMisses ); - assert( env == S3Global.envCache.aHead->env ); - assert( 0 != S3Global.envCache.aHead->g.cObj ); + assert( 1 == S3JniGlobal.metrics.envCacheMisses ); + assert( env == S3JniGlobal.envCache.aHead->env ); + assert( 0 != S3JniGlobal.envCache.aHead->g.cObj ); for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index a8a79df890..3d52b9a75e 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1120,7 +1120,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_00024Int32_2 (JNIEnv *, jclass, jobject, jint, jobject); /* diff --git a/ext/jni/src/org/sqlite/jni/CollationNeeded.java b/ext/jni/src/org/sqlite/jni/CollationNeeded.java index 1bd8be6290..85214a1d27 100644 --- a/ext/jni/src/org/sqlite/jni/CollationNeeded.java +++ b/ext/jni/src/org/sqlite/jni/CollationNeeded.java @@ -19,12 +19,10 @@ package org.sqlite.jni; public interface CollationNeeded { /** Has the same semantics as the C-level sqlite3_create_collation() - callback. Must not throw. + callback. - Pedantic note: the first argument to this function will always be - the same object reference which was passed to sqlite3_open() or - sqlite3_open_v2(), even if the client has managed to create other - Java-side references to the same C-level object. + If it throws, the exception message is passed on to the db and + the exception is suppressed. */ int xCollationNeeded(sqlite3 db, int eTextRep, String collationName); } diff --git a/ext/jni/src/org/sqlite/jni/ProgressHandler.java b/ext/jni/src/org/sqlite/jni/ProgressHandler.java index 8c463fe364..c806eebca0 100644 --- a/ext/jni/src/org/sqlite/jni/ProgressHandler.java +++ b/ext/jni/src/org/sqlite/jni/ProgressHandler.java @@ -19,7 +19,9 @@ package org.sqlite.jni; public interface ProgressHandler { /** Works as documented for the sqlite3_progress_handler() callback. - Must not throw. + + If it throws, the exception message is passed on to the db and + the exception is suppressed. */ int xCallback(); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index b3e435fa90..647989230f 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -382,6 +382,10 @@ public final class SQLite3Jni { public static native sqlite3_value sqlite3_column_value(@NotNull sqlite3_stmt stmt, int ndx); + /** + This functions like C's sqlite3_collation_needed16() because + Java's string type is compatible with that interface. + */ public static native int sqlite3_collation_needed(@NotNull sqlite3 db, @Nullable CollationNeeded callback); diff --git a/manifest b/manifest index 853b14685d..58404c9e1d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C JNI\sAPI\srenaming\sto\sbetter\smatch\sthe\sC\sAPI. -D 2023-08-07T10:01:59.163 +C Lots\sof\sJNI\sinternal\sAPI\srenaming,\sfor\sconsistency,\sand\smoving-around\sof\sutility\sfunctions.\sMake\sit\ssafe\sfor\smore\scallback\stypes\sto\sthrow. +D 2023-08-07T10:59:27.774 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,13 +232,13 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c e24884ce42955da0effd14c5dacf072cecaf2f10471f83384f43c0ba4ed94805 -F ext/jni/src/c/sqlite3-jni.h c39e30a8e06e93f7b84e3733202f24a75fce16875f3b2f1b89ae28e38e3dfcbb +F ext/jni/src/c/sqlite3-jni.c f494d60a14182107758aeebdc967bb5ba344738de592373947fc511ec3a65841 +F ext/jni/src/c/sqlite3-jni.h 6c06cdb1e43ce56544dfbe3335a46174dae15d03337d06e55aa7256f85d2ce1b F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 -F ext/jni/src/org/sqlite/jni/CollationNeeded.java ebc7cd96d46a70daa76016a308e80f70a3f21d3282787c8d139aa840fdcb1bd7 +F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 @@ -248,10 +248,10 @@ F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee F ext/jni/src/org/sqlite/jni/OutputPointer.java ebdd33d48064c3302d0d4a6dd345562a967f8420edad7c7509403be277d076a0 -F ext/jni/src/org/sqlite/jni/ProgressHandler.java 5979450e996416d28543f1d42634d308439565a99332a8bd84e424af667116cc +F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 7f15c3d17d61c19d51dfa32d810707eaa8c9149c05a364ad01fba6c2a6bda6f7 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21b828cb61984977c7b2a0cbd803f0c15dd79ff524ab94d3ce52648c011b2c9c F ext/jni/src/org/sqlite/jni/Tester1.java 55bf9c35c4a5649bdfb6ce940117d33ec24a6722bc252fadf7bc7102b9e94d6a F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2083,8 +2083,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ce82c42f151e38b23945e6f5dd99cb6a77b3c6440508f41abc35e9f6c29cd440 -R 7eccdfc2ef7173451e58d94e7f4df988 +P 6e0bd03d0ba9ee8422853241ba1c4e963d158d1f042855c0cb0026701907896e +R 2443bcafecde9596198577259670a0d3 U stephan -Z da2d66afb2e1f6b314be079f14f3192a +Z 19c29c5513623f28912ff9f11aaa44c7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 507b2c53f0..602d1af68f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6e0bd03d0ba9ee8422853241ba1c4e963d158d1f042855c0cb0026701907896e \ No newline at end of file +9a494394b9eb28cf88dc5e7075a4b8c682c8e14fdd6837b595bec8011d7e9e72 \ No newline at end of file From c7f7b45a154f143780e113a0450fd1c6fa2b5bec Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 11:18:44 +0000 Subject: [PATCH 073/148] When converting a Java exception to a db error message, use Throwable.toString() instead of getMessage() so that the exception type's name is included. More internal API renaming for consistency. FossilOrigin-Name: 2d44720d06d9e50cb037e92981d2473a3ad0b7560f2f5923d428f59de6fd6aaa --- ext/jni/src/c/sqlite3-jni.c | 82 ++++++++++++++++++++----------------- manifest | 12 +++--- manifest.uuid | 2 +- 3 files changed, 52 insertions(+), 44 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index b83e50b82c..1c17ed970e 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -440,8 +440,8 @@ struct S3JniAutoExtension { #endif /** State for various hook callbacks. */ -typedef struct S3JniHookState S3JniHookState; -struct S3JniHookState{ +typedef struct S3JniHook S3JniHook; +struct S3JniHook{ jobject jObj /* global ref to Java instance */; jmethodID midCallback /* callback method. Signature depends on jObj's type */; @@ -470,15 +470,15 @@ struct S3JniDb { to receive. */; char * zMainDbName /* Holds any string allocated on behave of SQLITE_DBCONFIG_MAINDBNAME. */; - S3JniHookState busyHandler; - S3JniHookState collation; - S3JniHookState collationNeeded; - S3JniHookState commitHook; - S3JniHookState progress; - S3JniHookState rollbackHook; - S3JniHookState trace; - S3JniHookState updateHook; - S3JniHookState authHook; + S3JniHook busyHandler; + S3JniHook collation; + S3JniHook collationNeeded; + S3JniHook commitHook; + S3JniHook progress; + S3JniHook rollbackHook; + S3JniHook trace; + S3JniHook updateHook; + S3JniHook authHook; #ifdef SQLITE_ENABLE_FTS5 jobject jFtsApi /* global ref to s3jni_fts5_api_from_db() */; #endif @@ -756,10 +756,18 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, } /** - Requires jx to be a Throwable. Calls its getMessage() method and + Requires jx to be a Throwable. Calls its toString() method and returns its value converted to a UTF-8 string. The caller owns the returned string and must eventually sqlite3_free() it. Returns 0 if there is a problem fetching the info or on OOM. + + Design note: we use toString() instead of getMessage() because the + former includes the exception type's name: + + Exception e = new RuntimeException("Hi"); + System.out.println(e.toString()); // java.lang.RuntimeException: Hi + System.out.println(e.getMessage()); // Hi + } */ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); @@ -767,7 +775,7 @@ static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ jstring msg; char * zMsg; jclass const klazz = (*env)->GetObjectClass(env, jx); - mid = (*env)->GetMethodID(env, klazz, "getMessage", "()Ljava/lang/String;"); + mid = (*env)->GetMethodID(env, klazz, "toString", "()Ljava/lang/String;"); IFTHREW{ EXCEPTION_REPORT; EXCEPTION_CLEAR; @@ -848,7 +856,7 @@ static void s3jni_call_xDestroy(JNIEnv * const env, jobject jObj, jclass klazz){ cleared. It is legal to call this when the object has no Java references. */ -static void S3JniHookState_unref(JNIEnv * const env, S3JniHookState * const s, int doXDestroy){ +static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDestroy){ if(doXDestroy && s->klazz && s->jObj){ s3jni_call_xDestroy(env, s->jObj, s->klazz); } @@ -876,7 +884,7 @@ static void S3JniDb_set_aside(S3JniDb * const s){ S3JniGlobal.perDb.aUsed = s->pNext; } sqlite3_free( s->zMainDbName ); -#define UNHOOK(MEMBER,XDESTROY) S3JniHookState_unref(env, &s->MEMBER, XDESTROY) +#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->MEMBER, XDESTROY) UNHOOK(trace, 0); UNHOOK(progress, 0); UNHOOK(commitHook, 0); @@ -1473,7 +1481,7 @@ static int CollationState_xCompare(void *pArg, int nLhs, const void *lhs, /* Collation finalizer for use by the sqlite3 internals. */ static void CollationState_xDestroy(void *pArg){ S3JniDb * const ps = pArg; - S3JniHookState_unref( ps->env, &ps->collation, 1 ); + S3JniHook_unref( ps->env, &ps->collation, 1 ); } /* State for sqlite3_result_java_object() and @@ -2031,24 +2039,24 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ int rc = 0; if(!ps) return (jint)SQLITE_NOMEM; if(jBusy){ - S3JniHookState * const pHook = &ps->busyHandler; + S3JniHook * const pHook = &ps->busyHandler; if(pHook->jObj && (*env)->IsSameObject(env, pHook->jObj, jBusy)){ /* Same object - this is a no-op. */ return 0; } - S3JniHookState_unref(env, pHook, 1); + S3JniHook_unref(env, pHook, 1); pHook->jObj = REF_G(jBusy); pHook->klazz = REF_G((*env)->GetObjectClass(env, jBusy)); pHook->midCallback = (*env)->GetMethodID(env, pHook->klazz, "xCallback", "(I)I"); IFTHREW { - S3JniHookState_unref(env, pHook, 0); + S3JniHook_unref(env, pHook, 0); rc = SQLITE_ERROR; } if(rc){ return rc; } }else{ - S3JniHookState_unref(env, &ps->busyHandler, 1); + S3JniHook_unref(env, &ps->busyHandler, 1); } return jBusy ? sqlite3_busy_handler(ps->pDb, s3jni_busy_handler, ps) @@ -2058,7 +2066,7 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); if( ps ){ - S3JniHookState_unref(env, &ps->busyHandler, 1); + S3JniHook_unref(env, &ps->busyHandler, 1); return sqlite3_busy_timeout(ps->pDb, (int)ms); } return SQLITE_MISUSE; @@ -2132,7 +2140,7 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, ps->collationNeeded.midCallback, ps->jDb, (jint)eTextRep, jName); IFTHREW{ - s3jni_db_exception(env, ps, 0, "collation-needed callback"); + s3jni_db_exception(env, ps, 0, "collation-needed callback threw"); } } UNREF_L(jName); @@ -2143,7 +2151,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ jclass klazz; jobject pOld = 0; jmethodID xCallback; - S3JniHookState * const pHook = &ps->collationNeeded; + S3JniHook * const pHook = &ps->collationNeeded; int rc; if( !ps ) return SQLITE_MISUSE; @@ -2154,7 +2162,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ } if( !jHook ){ UNREF_G(pOld); - memset(pHook, 0, sizeof(S3JniHookState)); + memset(pHook, 0, sizeof(S3JniHook)); sqlite3_collation_needed(ps->pDb, 0, 0); return 0; } @@ -2253,7 +2261,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobje jclass klazz; jobject pOld = 0; jmethodID xCallback; - S3JniHookState * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; + S3JniHook * const pHook = isCommit ? &ps->commitHook : &ps->rollbackHook; if(!ps){ s3jni_db_error(ps->pDb, SQLITE_NOMEM, 0); return 0; @@ -2269,7 +2277,7 @@ static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobje UNREF_G(pOld); pOld = tmp; } - memset(pHook, 0, sizeof(S3JniHookState)); + memset(pHook, 0, sizeof(S3JniHook)); if( isCommit ) sqlite3_commit_hook(ps->pDb, 0, 0); else sqlite3_rollback_hook(ps->pDb, 0, 0); return pOld; @@ -2396,7 +2404,7 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, jclass klazz; int rc; const char *zName; - S3JniHookState * pHook; + S3JniHook * pHook; if(!ps) return (jint)SQLITE_NOMEM; pHook = &ps->collation; klazz = (*env)->GetObjectClass(env, oCollation); @@ -2416,7 +2424,7 @@ JDECL(jint,1create_1collation)(JENV_CSELF, jobject jDb, pHook->jObj = REF_G(oCollation); pHook->klazz = REF_G(klazz); }else{ - S3JniHookState_unref(env, pHook, 1); + S3JniHook_unref(env, pHook, 1); } return (jint)rc; } @@ -2987,7 +2995,7 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, jstring const s1 = z1 ? (*env)->NewStringUTF(env, z1) : 0; jstring const s2 = z2 ? (*env)->NewStringUTF(env, z2) : 0; jstring const s3 = z3 ? (*env)->NewStringUTF(env, z3) : 0; - S3JniHookState const * const pHook = &ps->authHook; + S3JniHook const * const pHook = &ps->authHook; int rc; assert( pHook->jObj ); @@ -3006,11 +3014,11 @@ static int s3jni_xAuth(void* pState, int op,const char*z0, const char*z1, JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - S3JniHookState * const pHook = ps ? &ps->authHook : 0; + S3JniHook * const pHook = ps ? &ps->authHook : 0; if( !ps ) return SQLITE_MISUSE; else if( !jHook ){ - S3JniHookState_unref(env, pHook, 0); + S3JniHook_unref(env, pHook, 0); return (jint)sqlite3_set_authorizer( ps->pDb, 0, 0 ); }else{ int rc = 0; @@ -3019,7 +3027,7 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ /* Same object - this is a no-op. */ return 0; } - S3JniHookState_unref(env, pHook, 0); + S3JniHook_unref(env, pHook, 0); } pHook->jObj = REF_G(jHook); pHook->klazz = REF_G((*env)->GetObjectClass(env, jHook)); @@ -3032,12 +3040,12 @@ JDECL(jint,1set_1authorizer)(JENV_CSELF,jobject jDb, jobject jHook){ "Ljava/lang/String;" ")I"); IFTHREW { - S3JniHookState_unref(env, pHook, 0); + S3JniHook_unref(env, pHook, 0); return s3jni_db_error(ps->pDb, SQLITE_ERROR, "Error setting up Java parts of authorizer hook."); } rc = sqlite3_set_authorizer(ps->pDb, s3jni_xAuth, ps); - if( rc ) S3JniHookState_unref(env, pHook, 0); + if( rc ) S3JniHook_unref(env, pHook, 0); return rc; } } @@ -3178,7 +3186,7 @@ JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ jclass klazz; jobject pOld = 0; jmethodID xCallback; - S3JniHookState * const pHook = &ps->updateHook; + S3JniHook * const pHook = &ps->updateHook; if(!ps){ s3jni_db_error(ps->pDb, SQLITE_MISUSE, 0); return 0; @@ -3194,7 +3202,7 @@ JDECL(jobject,1update_1hook)(JENV_CSELF, jobject jDb, jobject jHook){ UNREF_G(pOld); pOld = tmp; } - memset(pHook, 0, sizeof(S3JniHookState)); + memset(pHook, 0, sizeof(S3JniHook)); sqlite3_update_hook(ps->pDb, 0, 0); return pOld; } @@ -3328,7 +3336,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ #define SO(T) printf("\tsizeof(" #T ") = %u\n", (unsigned)sizeof(T)) SO(void*); SO(S3JniEnvCache); - SO(S3JniHookState); + SO(S3JniHook); SO(S3JniDb); SO(S3JniClassNames); printf("\t(^^^ %u NativePointerHolder subclasses)\n", diff --git a/manifest b/manifest index 58404c9e1d..47b778161b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Lots\sof\sJNI\sinternal\sAPI\srenaming,\sfor\sconsistency,\sand\smoving-around\sof\sutility\sfunctions.\sMake\sit\ssafe\sfor\smore\scallback\stypes\sto\sthrow. -D 2023-08-07T10:59:27.774 +C When\sconverting\sa\sJava\sexception\sto\sa\sdb\serror\smessage,\suse\sThrowable.toString()\sinstead\sof\sgetMessage()\sso\sthat\sthe\sexception\stype's\sname\sis\sincluded.\sMore\sinternal\sAPI\srenaming\sfor\sconsistency. +D 2023-08-07T11:18:44.649 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c f494d60a14182107758aeebdc967bb5ba344738de592373947fc511ec3a65841 +F ext/jni/src/c/sqlite3-jni.c e6463b3fc8ef000d9a5dd1649fe96a4cfc5aac21a43276424cf28d72548c5921 F ext/jni/src/c/sqlite3-jni.h 6c06cdb1e43ce56544dfbe3335a46174dae15d03337d06e55aa7256f85d2ce1b F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d @@ -2083,8 +2083,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 6e0bd03d0ba9ee8422853241ba1c4e963d158d1f042855c0cb0026701907896e -R 2443bcafecde9596198577259670a0d3 +P 9a494394b9eb28cf88dc5e7075a4b8c682c8e14fdd6837b595bec8011d7e9e72 +R 482519d6e278134ac56541913ad9c20f U stephan -Z 19c29c5513623f28912ff9f11aaa44c7 +Z 5c40003605167b9b55a6a352144a1fa7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 602d1af68f..16b707af8d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9a494394b9eb28cf88dc5e7075a4b8c682c8e14fdd6837b595bec8011d7e9e72 \ No newline at end of file +2d44720d06d9e50cb037e92981d2473a3ad0b7560f2f5923d428f59de6fd6aaa \ No newline at end of file From 3897a882f7d86e27d66aba682e666ec1be1aa958 Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 21:04:13 +0000 Subject: [PATCH 074/148] Initial skeleton for adding an SQL-driven test script interpreter for the JNI bindings. FossilOrigin-Name: 2aa8f0edecd3fc30eec28987cdbf1003ace154ddc1447b6f8715ecf38d3b06fb --- ext/jni/GNUmakefile | 29 ++- .../src/org/sqlite/jni/tester/SQLTester.java | 81 +++++++ .../src/org/sqlite/jni/tester/TestScript.java | 36 ++++ .../jni/tester/test-script-interpreter.md | 200 ++++++++++++++++++ ext/jni/src/tests/000_first.test | 4 + manifest | 16 +- manifest.uuid | 2 +- 7 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/tester/SQLTester.java create mode 100644 ext/jni/src/org/sqlite/jni/tester/TestScript.java create mode 100644 ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md create mode 100644 ext/jni/src/tests/000_first.test diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index a1325434ae..b2f4dc03f9 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -24,6 +24,7 @@ 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.src.jni.tester := $(dir.src.jni)/tester $(dir.bld.c): mkdir -p $@ @@ -34,7 +35,9 @@ 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)) +SQLTester.java := src/org/sqlite/jni/tester/SQLTester.java +SQLite3Jni.class := $(SQLite3Jni.java:.java=.class) +SQLTester.class := $(SQLTester.java:.java=.class) ######################################################################## # The future of FTS5 customization in this API is as yet unclear. @@ -43,7 +46,7 @@ enable.fts5 ?= 1 # Be explicit about which Java files to compile so that we can work on # in-progress files without requiring them to be in a compilable statae. -JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ +JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\ BusyHandler.java \ Collation.java \ CollationNeeded.java \ @@ -64,7 +67,7 @@ JAVA_FILES := $(patsubst %,$(dir.src.jni)/%,\ ValueHolder.java \ ) ifeq (1,$(enable.fts5)) - JAVA_FILES += $(patsubst %,$(dir.src.jni)/%,\ + JAVA_FILES.main += $(patsubst %,$(dir.src.jni)/%,\ fts5_api.java \ fts5_extension_function.java \ fts5_tokenizer.java \ @@ -77,6 +80,16 @@ ifeq (1,$(enable.fts5)) TesterFts5.java \ ) endif +JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ + SQLTester.java \ + TestScript.java \ +) + +CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) +CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class) + +JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.tester) + CLASS_FILES := define DOTCLASS_DEPS $(1).class: $(1).java $(MAKEFILE) @@ -84,6 +97,7 @@ all: $(1).class CLASS_FILES += $(1).class endef $(foreach B,$(basename $(JAVA_FILES)),$(eval $(call DOTCLASS_DEPS,$(B)))) +$(CLASS_FILES.tester): $(CLASS_FILES.main) javac.flags ?= -Xlint:unchecked -Xlint:deprecation java.flags ?= jnicheck ?= 1 @@ -193,12 +207,21 @@ $(sqlite3-jni.dll): $(dir.bld.c) $(sqlite3-jni.c) $(SQLite3Jni.java) $(MAKEFILE) $(sqlite3-jni.c) -shared -o $@ all: $(sqlite3-jni.dll) +.PHONY: test test.flags ?= -v test: $(SQLite3Jni.class) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) +tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) +.PHONY: tester + +tester: $(CLASS_FILES.tester) $(sqlite3-jni.dll) + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.tester.SQLTester $(tester.scripts) + $(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 diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java new file mode 100644 index 0000000000..ce76eb31f4 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -0,0 +1,81 @@ +package org.sqlite.jni.tester; +import java.util.List; +import java.util.ArrayList; +import static org.sqlite.jni.SQLite3Jni.*; + +/** + This class provides an application which aims to implement the + rudimentary SQL-driven test tool described in the accompanying + test-script-interpreter.md. + + This is a work in progress. +*/ +public class SQLTester { + //! List of input script files. + private java.util.List listInFiles = new ArrayList<>(); + private boolean isVerbose = true; + + public SQLTester(){ + } + + public void setVerbose(boolean b){ + isVerbose = b; + } + + public static void out(T val){ + System.out.print(val); + } + + public static void outln(T val){ + System.out.println(val); + } + + @SuppressWarnings("unchecked") + public static void out(T... vals){ + int n = 0; + for(T v : vals) out((n++>0 ? " " : "")+v); + } + + @SuppressWarnings("unchecked") + public static void outln(T... vals){ + out(vals); + out("\n"); + } + + @SuppressWarnings("unchecked") + private void verbose(T... vals){ + if(isVerbose) outln(vals); + } + + //! Adds the given test script to the to-test list. + public void addTestScript(String filename){ + listInFiles.add(filename); + verbose("Added file",filename); + } + + public void runTests() throws Exception { + // process each input file + for(String f : listInFiles){ + verbose("Running test script",f); + final TestScript ts = new TestScript(f); + } + } + + public static void main(String[] argv) throws Exception{ + final SQLTester t = new SQLTester(); + for(String a : argv){ + if(a.startsWith("-")){ + final String flag = a.replaceFirst("-+",""); + if( flag.equals("verbose") ){ + t.setVerbose(true); + }else if( flag.equals("quiet") ) { + t.setVerbose(false); + }else{ + throw new IllegalArgumentException("Unhandled flag: "+flag); + } + } + t.addTestScript(a); + } + t.runTests(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java new file mode 100644 index 0000000000..45d42045c1 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -0,0 +1,36 @@ +package org.sqlite.jni.tester; +import java.io.*; +import java.nio.charset.StandardCharsets; +//import java.util.List; +//import java.util.ArrayList; + +/** + This class represents a single test script. It handles (or delegates) + its input and parsing. Iteration and evalution are deferred to other, + as-yet-non-existent, classes. + +*/ +public class TestScript { + //! Test script content. + private String content; + + private byte[] readFile(String filename) throws IOException { + return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); + } + + /** + Initializes the script with the content of the given file. + */ + public TestScript(String filename) throws IOException{ + this.content = new String(readFile(filename), + StandardCharsets.UTF_8); + } + + /** + Initializes the script with the given content, copied + at construction-time. + */ + public TestScript(StringBuffer content){ + this.content = content.toString(); + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md new file mode 100644 index 0000000000..36454f9d3b --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -0,0 +1,200 @@ +# Specifications For A Rudimentary SQLite Test Script Interpreter + +## Overview + +The purpose of the Test Script Interpreter is to read and interpret +script files that contain SQL commands and desired results. The +interpreter will check results and report an discrepencies found. + +The test script files are ASCII text files. The filename always ends with +".test". Each script is evaluated independently; context does not carry +forward from one script to the next. So, for example, the --null command +run in one test script does not cause any changes in the behavior of +subsequent test scripts. All open database connections are closed +at the end of each test script. All database files created by a test +script are deleted when the script finishes. + +## Parsing Rules: + + 1. Ignore the entire script if the script does not contain the + string "SCRIPT_MODULE_NAME:". + + 2. Ignore any script that contains the character sequence "\\n\|" + (0x0a, 0x7c). In other words, ignore scripts that contain the + pipe character at the beginning of a line. Such lines represent + test database file content in the "dbtotxt" format. We might add + support for this later, but omit it for the first version. + + 3. Ignore individual lines that begin with '#' (C-preprocessor lines). + + 4. If a line begins with exactly two minus signs followed by a + lowercase letter, that is a command. Process commands as described + below. + + 5. All other lines should be accumulated into the "input buffer". + The various commands will have access to this input buffer. + Some commands will reset the buffer. + +## Commands: + +Each command looks like an SQL comment. The command begins at the left +margin (no leading space) and starts with exactly 2 minus signs ("-"). +The command name consists of lowercase letters and maybe a "-" or two. +Some commands have arguments. +The arguments are separated from the command name by one or more spaces. + +Commands have access to the input buffer and might reset the input buffer. +The command can also optionally read (and consume) additional text from +script that comes after the command. + +Unknown or unrecognized commands should cause an error message to be +printed and execution to stop. + +The initial implemention will only recognize a few commands. Other +commands may be added later. The following is the initial set of +commands: + +### The --testcase command + +Every test case starts with a --testcase command. The --testcase command +resets both the "input buffer" and the "result buffer". +The argument to the --testcase command is the +name of the test case. That test case name is used for logging and debugging +and when printing errors. + +### The --result command + +The --result command tries to execute the text in the input buffer as SQL. +For each row of result coming out of this SQL, the text of that result is +appended to the "result buffer". If a result row contains multiple columns, +the columns are processed from left to right. For each column, text is +appended to the result buffer according to the following rules: + + * If the result buffer already contains some text, append a space. + (In this way, all column values and all row values are separated from + each other by a single space.) + + * If the sqlite3_column_text() returns NULL, then append "nil" - or + some other text that is specified by the --null command - and skip + all subsequent rules. + + * If sqlite3_column_text() does not contain any special characters, + append it to the result buffer without any formatting and skip all + subsequent rules. + + * If sqlite3_column_text() does not contains curly braces, then put + the text inside of `{...}` and append it and skip all subsequent rules. + + * Append the text within double-quotes (`"..."`) and within the text + escape '"' and '\\' by prepending a single '\\' and escape any + control characters (characters less than 0x20) using octal notation: + '\\NNN'. + +If an error is encountered while running the SQL, then append the +symbolic C-preprocessor name for the error +code (ex: "SQLITE_CONSTRAINT") as if it were a column value. Then append +the error message text as if it where a column value. Then stop processing. + +After the SQL text has been run, compare the content of the result buffer +against the argument to the --result command and report a testing error if +there are any differences. + +The --result command resets the input buffer, but it does not reset +the result buffer. This distinction does not matter for the --result +command itself, but it is important for related commands like --glob +and --notglob. Sometimes test cases will contains a bunch of SQL +followed by multiple --glob and/or --notglob statements. All of the +globs should be evaluted agains the result buffer correct, but the SQL +should only be run once. This is accomplished by resetting the input +buffer but not the result buffer. + +### The --glob command + +The --glob command works just like --result except that the argument to +--glob is interpreted as a TEST-GLOB pattern and the results are compared +using that glob pattern rather than using strcmp(). Other than that, +the two operate the same. + +The TEST-GLOB pattern is slightly different for a standard GLOB: + + * The '*' character matches zero or more characters. + + * The '?' character matches any single character + + * The '[...]' character sequence machines a single character + in between the brackets. + + * The '#' character matches one or more digits (This is the main + difference between standard unix-glob and TEST-GLOB. unix-glob + does not have this feature. It was added to because it comes + up a lot during SQLite testing.) + +### The --notglob command + +The --notglob command works just like --glob except that it reports an +error if the GLOB does match, rather than if the GLOB does not matches. + +### The --oom command + +This command is to be used for out-of-memory testing. It means that +OOM errors should be simulated to ensure that SQLite is able to deal with +them. This command can be silently ignored for now. We might add support +for this later. + +### The --tableresult command + +The --tableresult command works like --glob except that the GLOB pattern +to be matched is taken from subsequent lines of the input script up to +the next --end. Every span of one or more whitespace characters in this +pattern text is collapsed into a single space (0x20). +Leading and trailing whitespace are removed from the pattern. +The --end that ends the GLOB pattern is not part of the GLOB pattern, but +the --end is consumed from the script input. + +### The --new and --open commands + +The --new and --open commands cause a database file to be opened. +The name of the file is the argument to the command. The --new command +opens an initially empty database (it deletes the file before opening it) +whereas the --open command opens an existing database if it already +exists. + +### The --db command + +The script interpreter can have up to 7 different SQLite database +connections open at a time. The --db command is used to switch between +them. The argument to --db is an integer between 0 and 6 that selects +which database connection to use moving forward. + +### The --close command + +The --close command causes an existing database connetion to close. +This command is a no-op if the database connection is not currently +open. There can be up to 7 different database connections, numbered +0 through 6. The number of the database connection to close is an argument +to the --close command. Or if the argument to --close is "all" then all +open database connections are closed. + +### The --null command + +The NULL command changes the text that is used to represent SQL NULL +values in the result buffer. + +### The --run command + +The --run command executes text in the input buffer as if it where SQL. +However, nothing is added to the result buffer. Any output from the SQL +is silently ignored. Errors in the SQL are silently ignored. + +The --run command normally executes the SQL in the current database +connection. However, if --run has an argument that is an integer between +0 and 6 then the SQL is run in the alternative database connection specified +by that argument. + +### The --json and --json-block commands + +The --json and --json-block commands work like --result and --tableresult, +respectively. The difference is that column values are appended to the +result buffer literally, without every enclosing the values in `{...}` or +`"..."` and without escaping any characters in the column value and comparison +is always an exact strcmp() not a GLOB. diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test new file mode 100644 index 0000000000..b66c32ce89 --- /dev/null +++ b/ext/jni/src/tests/000_first.test @@ -0,0 +1,4 @@ +/* A script for testing the org.sqlite.jni.tester infrastructure */ + +# this line is ignored + diff --git a/manifest b/manifest index 47b778161b..42c5e1016a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C When\sconverting\sa\sJava\sexception\sto\sa\sdb\serror\smessage,\suse\sThrowable.toString()\sinstead\sof\sgetMessage()\sso\sthat\sthe\sexception\stype's\sname\sis\sincluded.\sMore\sinternal\sAPI\srenaming\sfor\sconsistency. -D 2023-08-07T11:18:44.649 +C Initial\sskeleton\sfor\sadding\san\sSQL-driven\stest\sscript\sinterpreter\sfor\sthe\sJNI\sbindings. +D 2023-08-07T21:04:13.706 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,7 @@ 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 61d9bbc179a49523a142928455b3297779b9c40f25783ecf1538279e426cbc99 +F ext/jni/GNUmakefile e492513ab2fc6da4f01d6745c852d3ef0afa336994e91a513b523ae8ececcde8 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c e6463b3fc8ef000d9a5dd1649fe96a4cfc5aac21a43276424cf28d72548c5921 F ext/jni/src/c/sqlite3-jni.h 6c06cdb1e43ce56544dfbe3335a46174dae15d03337d06e55aa7256f85d2ce1b @@ -264,6 +264,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java ff3729426704626a6019d97bfee512a83f253c F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d51db80b241ba90dea253b453cd7bb44648fdf0bb84370225f63ac541e727e4a +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 67721f78f753c719e465db07abd928fa3c96ebc3917c797b42d21d89aa671a82 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ba3cf6584783939c8797a67203e63ec588430fdc0b7719f24873e6731c6d0445 +F ext/jni/src/tests/000_first.test 9d20eef5d8985ce32dc039004f186a973ca629afee9ab2134f4ec18d1748e426 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2083,8 +2087,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 9a494394b9eb28cf88dc5e7075a4b8c682c8e14fdd6837b595bec8011d7e9e72 -R 482519d6e278134ac56541913ad9c20f +P 2d44720d06d9e50cb037e92981d2473a3ad0b7560f2f5923d428f59de6fd6aaa +R 85efb1bd754dc306bbc436950f6f48cb U stephan -Z 5c40003605167b9b55a6a352144a1fa7 +Z 24f4287e10d577104a22d65944b7f529 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 16b707af8d..f2bb1ef1b8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2d44720d06d9e50cb037e92981d2473a3ad0b7560f2f5923d428f59de6fd6aaa \ No newline at end of file +2aa8f0edecd3fc30eec28987cdbf1003ace154ddc1447b6f8715ecf38d3b06fb \ No newline at end of file From 70679d135db24e2471a77ac07b5f17fb1b8d4a1e Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 22:02:43 +0000 Subject: [PATCH 075/148] SQLTester can now read a script and strip it of all noise content. FossilOrigin-Name: 59bd392817ac69ffdf60ab7a2094b0d616bf593da060b6acf1b4ce9837847fcb --- ext/jni/GNUmakefile | 1 + ext/jni/src/org/sqlite/jni/tester/Outer.java | 40 +++++++++++ .../src/org/sqlite/jni/tester/SQLTester.java | 30 ++------ .../src/org/sqlite/jni/tester/TestScript.java | 71 ++++++++++++++++++- ext/jni/src/tests/000_first.test | 6 ++ manifest | 19 ++--- manifest.uuid | 2 +- 7 files changed, 133 insertions(+), 36 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/tester/Outer.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index b2f4dc03f9..4e537f123b 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -81,6 +81,7 @@ ifeq (1,$(enable.fts5)) ) endif JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ + Outer.java \ SQLTester.java \ TestScript.java \ ) diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java new file mode 100644 index 0000000000..f099fe3660 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/Outer.java @@ -0,0 +1,40 @@ +package org.sqlite.jni.tester; + +public class Outer { + public boolean isVerbose = true; + + public static void out(T val){ + System.out.print(val); + } + + public static void outln(T val){ + System.out.println(val); + } + + @SuppressWarnings("unchecked") + public static void out(T... vals){ + int n = 0; + for(T v : vals) out((n++>0 ? " " : "")+v); + } + + @SuppressWarnings("unchecked") + public static void outln(T... vals){ + out(vals); + out("\n"); + } + + @SuppressWarnings("unchecked") + public Outer verbose(T... vals){ + if(isVerbose) outln(vals); + return this; + } + + public void setVerbose(boolean b){ + isVerbose = b; + } + + public boolean getVerbose(){ + return isVerbose; + } + +} diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index ce76eb31f4..69c122da25 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -12,39 +12,19 @@ import static org.sqlite.jni.SQLite3Jni.*; */ public class SQLTester { //! List of input script files. - private java.util.List listInFiles = new ArrayList<>(); - private boolean isVerbose = true; + private final java.util.List listInFiles = new ArrayList<>(); + private final Outer outer = new Outer(); public SQLTester(){ } public void setVerbose(boolean b){ - isVerbose = b; - } - - public static void out(T val){ - System.out.print(val); - } - - public static void outln(T val){ - System.out.println(val); - } - - @SuppressWarnings("unchecked") - public static void out(T... vals){ - int n = 0; - for(T v : vals) out((n++>0 ? " " : "")+v); - } - - @SuppressWarnings("unchecked") - public static void outln(T... vals){ - out(vals); - out("\n"); + this.outer.setVerbose(b); } @SuppressWarnings("unchecked") private void verbose(T... vals){ - if(isVerbose) outln(vals); + this.outer.verbose(vals); } //! Adds the given test script to the to-test list. @@ -58,6 +38,8 @@ public class SQLTester { for(String f : listInFiles){ verbose("Running test script",f); final TestScript ts = new TestScript(f); + ts.setVerbose(this.outer.getVerbose()); + ts.dump(); } } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 45d42045c1..ab2366bd7f 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -1,6 +1,8 @@ package org.sqlite.jni.tester; +import java.util.List; +import java.util.ArrayList; import java.io.*; -import java.nio.charset.StandardCharsets; +import java.util.regex.*; //import java.util.List; //import java.util.ArrayList; @@ -13,6 +15,7 @@ import java.nio.charset.StandardCharsets; public class TestScript { //! Test script content. private String content; + private final Outer outer = new Outer(); private byte[] readFile(String filename) throws IOException { return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); @@ -23,7 +26,7 @@ public class TestScript { */ public TestScript(String filename) throws IOException{ this.content = new String(readFile(filename), - StandardCharsets.UTF_8); + java.nio.charset.StandardCharsets.UTF_8); } /** @@ -33,4 +36,68 @@ public class TestScript { public TestScript(StringBuffer content){ this.content = content.toString(); } + + public void setVerbose(boolean b){ + this.outer.setVerbose(b); + } + + @SuppressWarnings("unchecked") + private TestScript verbose(T... vals){ + this.outer.verbose(vals); + return this; + } + + /** + A quick-and-dirty approach to chopping a script up into individual + commands. The primary problem with this is that it will remove any + C-style comments from expected script output, which might or might not + be a real problem. + */ + private String chunkContent(){ + final String sCComment = + "[/][*]([*](?![/])|[^*])*[*][/]" + //"/\\*[^/*]*(?:(?!/\\*|\\*/)[/*][^/*]*)*\\*/" + ; + final String s3Dash = "^---[^\\n]*\\n"; + final String sTclComment = "^#[^\\n]*\\n"; + final String sEmptyLine = "^\\n"; + final String sCommand = "^--.*$"; + final List lPats = new ArrayList<>(); + lPats.add(sCComment); + lPats.add(s3Dash); + lPats.add(sTclComment); + lPats.add(sEmptyLine); + //lPats.add(sCommand); + verbose("Content:").verbose(content).verbose(""); + String tmp = content; + for( String s : lPats ){ + final Pattern p = Pattern.compile( + s, Pattern.MULTILINE + ); + final Matcher m = p.matcher(tmp); + verbose("Pattern {{{",p.pattern(),"}}} with flags", + ""+p.flags(),"matches:" + ); + int n = 0; + while(m.find()){ + verbose("#"+(++n)+"\t",m.group(0).trim()); + } + tmp = m.replaceAll(""); + } + //final Pattern patCComments = new Pattern(); + //tmp = content.replace(sCComment,""); + //tmp = tmp.replace(s3Dash,""); + //tmp = tmp.replace(sTclComment,""); + //tmp = tmp.replace(sEmptyLine,""); + return tmp; + } + + /** + A debug-only function which dumps the content of the test script + in some form or other (possibly mangled from its original). + */ + public void dump(){ + String s = this.chunkContent(); + this.verbose("chunked script:").verbose(s).verbose(""); + } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index b66c32ce89..9b5c75c01f 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -2,3 +2,9 @@ # this line is ignored +--null NULL +--- also ignored +--testcase second +select 1 +--result /* ignored */ +1 diff --git a/manifest b/manifest index 42c5e1016a..d287075a5c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\sskeleton\sfor\sadding\san\sSQL-driven\stest\sscript\sinterpreter\sfor\sthe\sJNI\sbindings. -D 2023-08-07T21:04:13.706 +C SQLTester\scan\snow\sread\sa\sscript\sand\sstrip\sit\sof\sall\snoise\scontent. +D 2023-08-07T22:02:43.384 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,7 @@ 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 e492513ab2fc6da4f01d6745c852d3ef0afa336994e91a513b523ae8ececcde8 +F ext/jni/GNUmakefile 2b800c74db98b64b63ec1da48dc4a27738f88951f0ca43011288abf80c1b5e80 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c e6463b3fc8ef000d9a5dd1649fe96a4cfc5aac21a43276424cf28d72548c5921 F ext/jni/src/c/sqlite3-jni.h 6c06cdb1e43ce56544dfbe3335a46174dae15d03337d06e55aa7256f85d2ce1b @@ -264,10 +264,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java ff3729426704626a6019d97bfee512a83f253c F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d51db80b241ba90dea253b453cd7bb44648fdf0bb84370225f63ac541e727e4a -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 67721f78f753c719e465db07abd928fa3c96ebc3917c797b42d21d89aa671a82 +F ext/jni/src/org/sqlite/jni/tester/Outer.java 8931ff9f152d22a822ff98831a4e924da48016ff1f1f84042390a6f51ad7b48f +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java edcab1ea3d7848523416b881061f8095e8f7ae2a626e07947a55a4215898e040 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 470e5c08d8badfa3194d06960fe830eb54fd78d2e086bb1f270af499ffea5f25 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ba3cf6584783939c8797a67203e63ec588430fdc0b7719f24873e6731c6d0445 -F ext/jni/src/tests/000_first.test 9d20eef5d8985ce32dc039004f186a973ca629afee9ab2134f4ec18d1748e426 +F ext/jni/src/tests/000_first.test 9a6622455cc4be00d332be655e0d2d5cf07a2d2b041f8d1950f66bda4873deed F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2087,8 +2088,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2d44720d06d9e50cb037e92981d2473a3ad0b7560f2f5923d428f59de6fd6aaa -R 85efb1bd754dc306bbc436950f6f48cb +P 2aa8f0edecd3fc30eec28987cdbf1003ace154ddc1447b6f8715ecf38d3b06fb +R f4841d676ff176af3cb93558f987461f U stephan -Z 24f4287e10d577104a22d65944b7f529 +Z c343b0e8d67b2614399479523c827e4a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f2bb1ef1b8..cd524757ae 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2aa8f0edecd3fc30eec28987cdbf1003ace154ddc1447b6f8715ecf38d3b06fb \ No newline at end of file +59bd392817ac69ffdf60ab7a2094b0d616bf593da060b6acf1b4ce9837847fcb \ No newline at end of file From fdeaee5f577be7e4d95d9b802d8337dc3affc16a Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 22:32:22 +0000 Subject: [PATCH 076/148] SQLTester can now split a test script into a series of individual commands. FossilOrigin-Name: d3d1accc8b4ba0cd396ee3a58d9710a54b8e1d1b171d67595d4ef1fc7faea8cb --- .../src/org/sqlite/jni/tester/TestScript.java | 66 ++++++++++++------- ext/jni/src/tests/000_first.test | 3 + manifest | 14 ++-- manifest.uuid | 2 +- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index ab2366bd7f..a415b917a9 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -21,12 +21,15 @@ public class TestScript { return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); } + private void setContent(String c){ + content = c; + } /** Initializes the script with the content of the given file. */ public TestScript(String filename) throws IOException{ - this.content = new String(readFile(filename), - java.nio.charset.StandardCharsets.UTF_8); + setContent(new String(readFile(filename), + java.nio.charset.StandardCharsets.UTF_8)); } /** @@ -34,16 +37,16 @@ public class TestScript { at construction-time. */ public TestScript(StringBuffer content){ - this.content = content.toString(); + setContent(content.toString()); } public void setVerbose(boolean b){ - this.outer.setVerbose(b); + outer.setVerbose(b); } @SuppressWarnings("unchecked") private TestScript verbose(T... vals){ - this.outer.verbose(vals); + outer.verbose(vals); return this; } @@ -53,43 +56,53 @@ public class TestScript { C-style comments from expected script output, which might or might not be a real problem. */ - private String chunkContent(){ + private List chunkContent(String input){ final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]" //"/\\*[^/*]*(?:(?!/\\*|\\*/)[/*][^/*]*)*\\*/" ; - final String s3Dash = "^---[^\\n]*\\n"; + final String s3Dash = "^---+[^\\n]*\\n"; final String sTclComment = "^#[^\\n]*\\n"; final String sEmptyLine = "^\\n"; - final String sCommand = "^--.*$"; final List lPats = new ArrayList<>(); lPats.add(sCComment); lPats.add(s3Dash); lPats.add(sTclComment); lPats.add(sEmptyLine); - //lPats.add(sCommand); - verbose("Content:").verbose(content).verbose(""); - String tmp = content; + //verbose("Content:").verbose(input).verbose(""); + String tmp = input; for( String s : lPats ){ final Pattern p = Pattern.compile( s, Pattern.MULTILINE ); final Matcher m = p.matcher(tmp); - verbose("Pattern {{{",p.pattern(),"}}} with flags", + /*verbose("Pattern {{{",p.pattern(),"}}} with flags", ""+p.flags(),"matches:" - ); + );*/ int n = 0; - while(m.find()){ - verbose("#"+(++n)+"\t",m.group(0).trim()); - } + //while( m.find() ) verbose("#"+(++n)+"\t",m.group(0).trim()); tmp = m.replaceAll(""); } - //final Pattern patCComments = new Pattern(); - //tmp = content.replace(sCComment,""); - //tmp = tmp.replace(s3Dash,""); - //tmp = tmp.replace(sTclComment,""); - //tmp = tmp.replace(sEmptyLine,""); - return tmp; + // Chunk the newly-stripped text into individual commands. + final String sCommand = "^--"; + final List rc = new ArrayList<>(); + final Pattern p = Pattern.compile( + sCommand, Pattern.MULTILINE + ); + final Matcher m = p.matcher(tmp); + int ndxPrev = 0, pos = 0; + String chunk; + while( m.find() ){ + pos = m.start(); + chunk = tmp.substring(ndxPrev, pos).trim(); + if( !chunk.isEmpty() ) rc.add( chunk ); + ndxPrev = pos + 2; + } + if( ndxPrev != pos + 2 ){ + chunk = tmp.substring(ndxPrev, tmp.length()).trim(); + if( !chunk.isEmpty() ) rc.add( chunk ); + } + return rc; } /** @@ -97,7 +110,12 @@ public class TestScript { in some form or other (possibly mangled from its original). */ public void dump(){ - String s = this.chunkContent(); - this.verbose("chunked script:").verbose(s).verbose(""); + List list = chunkContent(content); + verbose("script chunked by command:"); + int n = 0; + for(String c : list){ + verbose("#"+(++n),c); + } + verbose(""); } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 9b5c75c01f..0037ef3ec3 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -4,6 +4,9 @@ --null NULL --- also ignored +--testcase first +input for the first +command; --testcase second select 1 --result /* ignored */ diff --git a/manifest b/manifest index d287075a5c..3758d4f51e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C SQLTester\scan\snow\sread\sa\sscript\sand\sstrip\sit\sof\sall\snoise\scontent. -D 2023-08-07T22:02:43.384 +C SQLTester\scan\snow\ssplit\sa\stest\sscript\sinto\sa\sseries\sof\sindividual\scommands. +D 2023-08-07T22:32:22.258 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,9 +266,9 @@ F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449 F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 8931ff9f152d22a822ff98831a4e924da48016ff1f1f84042390a6f51ad7b48f F ext/jni/src/org/sqlite/jni/tester/SQLTester.java edcab1ea3d7848523416b881061f8095e8f7ae2a626e07947a55a4215898e040 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 470e5c08d8badfa3194d06960fe830eb54fd78d2e086bb1f270af499ffea5f25 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 00007d167ce5b40506a624bad1fb8571a0b975285849a7bd8fd7c0ebcfb3f785 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ba3cf6584783939c8797a67203e63ec588430fdc0b7719f24873e6731c6d0445 -F ext/jni/src/tests/000_first.test 9a6622455cc4be00d332be655e0d2d5cf07a2d2b041f8d1950f66bda4873deed +F ext/jni/src/tests/000_first.test a06b72b6815246a21f6a4b14126bbc40b9cd1e3b03410431ed50203cfa942e9b F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2088,8 +2088,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2aa8f0edecd3fc30eec28987cdbf1003ace154ddc1447b6f8715ecf38d3b06fb -R f4841d676ff176af3cb93558f987461f +P 59bd392817ac69ffdf60ab7a2094b0d616bf593da060b6acf1b4ce9837847fcb +R 63407ef8cfa2823943afd16b1a637995 U stephan -Z c343b0e8d67b2614399479523c827e4a +Z 2b2535b40ad95e4630d07d7436d967c8 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index cd524757ae..08b4c777f7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -59bd392817ac69ffdf60ab7a2094b0d616bf593da060b6acf1b4ce9837847fcb \ No newline at end of file +d3d1accc8b4ba0cd396ee3a58d9710a54b8e1d1b171d67595d4ef1fc7faea8cb \ No newline at end of file From b7f75b7bb2266cc68b149a6fa575d212d768effc Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 23:04:17 +0000 Subject: [PATCH 077/148] SQLTester now ignores tests which contain constructs specified in the spec doc. FossilOrigin-Name: ecaeee652aa2cc6893ded9231d7e9b2783465516016740b307b74e4e81598ae3 --- .../src/org/sqlite/jni/tester/SQLTester.java | 2 +- .../src/org/sqlite/jni/tester/TestScript.java | 73 +++++++++++++------ .../jni/tester/test-script-interpreter.md | 6 +- ext/jni/src/tests/010_ignored.test | 4 + manifest | 17 +++-- manifest.uuid | 2 +- 6 files changed, 68 insertions(+), 36 deletions(-) create mode 100644 ext/jni/src/tests/010_ignored.test diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 69c122da25..4359122821 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -36,9 +36,9 @@ public class SQLTester { public void runTests() throws Exception { // process each input file for(String f : listInFiles){ - verbose("Running test script",f); final TestScript ts = new TestScript(f); ts.setVerbose(this.outer.getVerbose()); + verbose("Test",ts.getName(),"..."); ts.dump(); } } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index a415b917a9..b4f8ec28be 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -14,30 +14,48 @@ import java.util.regex.*; */ public class TestScript { //! Test script content. + private String name; private String content; + private List chunks = null; private final Outer outer = new Outer(); + private boolean ignored = false; - private byte[] readFile(String filename) throws IOException { + private byte[] readFile(String filename) throws Exception { return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); } private void setContent(String c){ content = c; + ignored = shouldBeIgnored(c); + chunks = chunkContent(); } /** Initializes the script with the content of the given file. */ - public TestScript(String filename) throws IOException{ + public TestScript(String filename) throws Exception{ setContent(new String(readFile(filename), java.nio.charset.StandardCharsets.UTF_8)); + name = filename; } /** - Initializes the script with the given content, copied - at construction-time. + Initializes the script with the given content, copied at + construction-time. The first argument is a filename for that + content. It need not refer to a real file - it's for display + purposes only. */ - public TestScript(StringBuffer content){ + public TestScript(String virtualName, StringBuffer content) + throws RuntimeException { setContent(content.toString()); + name = virtualName; + } + + public String getName(){ + return name; + } + + public boolean isIgnored(){ + return ignored; } public void setVerbose(boolean b){ @@ -50,17 +68,23 @@ public class TestScript { return this; } + /** + Returns true if the given script content should be ignored + (because it contains certain content which indicates such). + */ + public static boolean shouldBeIgnored(String content){ + return content.indexOf("SCRIPT_MODULE_NAME")>=0 + || content.indexOf("\n|")>=0; + } + /** A quick-and-dirty approach to chopping a script up into individual - commands. The primary problem with this is that it will remove any - C-style comments from expected script output, which might or might not - be a real problem. - */ - private List chunkContent(String input){ - final String sCComment = - "[/][*]([*](?![/])|[^*])*[*][/]" - //"/\\*[^/*]*(?:(?!/\\*|\\*/)[/*][^/*]*)*\\*/" - ; + commands and their inputs. + */ + private List chunkContent(){ + if( ignored ) return null; + // First, strip out any content which we know we can ignore... + final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; final String s3Dash = "^---+[^\\n]*\\n"; final String sTclComment = "^#[^\\n]*\\n"; final String sEmptyLine = "^\\n"; @@ -69,8 +93,8 @@ public class TestScript { lPats.add(s3Dash); lPats.add(sTclComment); lPats.add(sEmptyLine); - //verbose("Content:").verbose(input).verbose(""); - String tmp = input; + //verbose("Content:").verbose(content).verbose(""); + String tmp = content; for( String s : lPats ){ final Pattern p = Pattern.compile( s, Pattern.MULTILINE @@ -83,7 +107,7 @@ public class TestScript { //while( m.find() ) verbose("#"+(++n)+"\t",m.group(0).trim()); tmp = m.replaceAll(""); } - // Chunk the newly-stripped text into individual commands. + // Chunk the newly-cleaned text into individual commands and their input... final String sCommand = "^--"; final List rc = new ArrayList<>(); final Pattern p = Pattern.compile( @@ -110,12 +134,15 @@ public class TestScript { in some form or other (possibly mangled from its original). */ public void dump(){ - List list = chunkContent(content); - verbose("script chunked by command:"); - int n = 0; - for(String c : list){ - verbose("#"+(++n),c); + if( null==chunks ){ + verbose("This contains content which forces it to be ignored."); + }else{ + verbose("script commands:"); + int n = 0; + for(String c : chunks){ + verbose("#"+(++n),c); + } + verbose(""); } - verbose(""); } } diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 36454f9d3b..d8610a68b8 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -17,7 +17,7 @@ script are deleted when the script finishes. ## Parsing Rules: 1. Ignore the entire script if the script does not contain the - string "SCRIPT_MODULE_NAME:". + string "SCRIPT_MODULE_NAME:". 2. Ignore any script that contains the character sequence "\\n\|" (0x0a, 0x7c). In other words, ignore scripts that contain the @@ -37,14 +37,14 @@ script are deleted when the script finishes. ## Commands: -Each command looks like an SQL comment. The command begins at the left +Each command looks like an SQL comment. The command begins at the left margin (no leading space) and starts with exactly 2 minus signs ("-"). The command name consists of lowercase letters and maybe a "-" or two. Some commands have arguments. The arguments are separated from the command name by one or more spaces. Commands have access to the input buffer and might reset the input buffer. -The command can also optionally read (and consume) additional text from +The command can also optionally read (and consume) additional text from script that comes after the command. Unknown or unrecognized commands should cause an error message to be diff --git a/ext/jni/src/tests/010_ignored.test b/ext/jni/src/tests/010_ignored.test new file mode 100644 index 0000000000..fe15c547c5 --- /dev/null +++ b/ext/jni/src/tests/010_ignored.test @@ -0,0 +1,4 @@ +/* This script must be marked as ignored because it contains + content which triggers that condition. */ + +| diff --git a/manifest b/manifest index 3758d4f51e..d21ad98114 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C SQLTester\scan\snow\ssplit\sa\stest\sscript\sinto\sa\sseries\sof\sindividual\scommands. -D 2023-08-07T22:32:22.258 +C SQLTester\snow\signores\stests\swhich\scontain\sconstructs\sspecified\sin\sthe\sspec\sdoc. +D 2023-08-07T23:04:17.593 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -265,10 +265,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 8931ff9f152d22a822ff98831a4e924da48016ff1f1f84042390a6f51ad7b48f -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java edcab1ea3d7848523416b881061f8095e8f7ae2a626e07947a55a4215898e040 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 00007d167ce5b40506a624bad1fb8571a0b975285849a7bd8fd7c0ebcfb3f785 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ba3cf6584783939c8797a67203e63ec588430fdc0b7719f24873e6731c6d0445 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ee4ef9c65c9eedf6bfd68ad807346592ee01faf7fad16c3f18c462ed4c98c38 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 374bdad5cd0597aa9e20e5b8abd077a41d92caae6190ce383c250cb08859e7e4 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3 F ext/jni/src/tests/000_first.test a06b72b6815246a21f6a4b14126bbc40b9cd1e3b03410431ed50203cfa942e9b +F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2088,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 59bd392817ac69ffdf60ab7a2094b0d616bf593da060b6acf1b4ce9837847fcb -R 63407ef8cfa2823943afd16b1a637995 +P d3d1accc8b4ba0cd396ee3a58d9710a54b8e1d1b171d67595d4ef1fc7faea8cb +R d5c9761c7876583110d4d0a8a0b413b4 U stephan -Z 2b2535b40ad95e4630d07d7436d967c8 +Z e82e467651a46849ab5637959c044f1b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 08b4c777f7..b3ed60e6df 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d3d1accc8b4ba0cd396ee3a58d9710a54b8e1d1b171d67595d4ef1fc7faea8cb \ No newline at end of file +ecaeee652aa2cc6893ded9231d7e9b2783465516016740b307b74e4e81598ae3 \ No newline at end of file From e9ed375876f7ed4ff4eef80f6852ea2e914c643f Mon Sep 17 00:00:00 2001 From: stephan Date: Mon, 7 Aug 2023 23:59:08 +0000 Subject: [PATCH 078/148] Add command dispatcher to SQLTester. FossilOrigin-Name: e0a06931e91459ea43fed2954568bfafa7ad6b794fcff66e0d3bf0ed181db386 --- .../src/org/sqlite/jni/tester/SQLTester.java | 73 ++++++++++++++++++- .../src/org/sqlite/jni/tester/TestScript.java | 56 ++++++++++++-- ext/jni/src/tests/000_first.test | 2 + manifest | 16 ++-- manifest.uuid | 2 +- 5 files changed, 131 insertions(+), 18 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 4359122821..9ff5bc1ff4 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -1,3 +1,17 @@ +/* +** 2023-08-08 +** +** 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 the main application entry pointer for the +** SQLTester framework. +*/ package org.sqlite.jni.tester; import java.util.List; import java.util.ArrayList; @@ -14,6 +28,7 @@ public class SQLTester { //! List of input script files. private final java.util.List listInFiles = new ArrayList<>(); private final Outer outer = new Outer(); + private final StringBuilder inputBuffer = new StringBuilder(); public SQLTester(){ } @@ -23,10 +38,15 @@ public class SQLTester { } @SuppressWarnings("unchecked") - private void verbose(T... vals){ + public void verbose(T... vals){ this.outer.verbose(vals); } + @SuppressWarnings("unchecked") + public void outln(T... vals){ + this.outer.outln(vals); + } + //! Adds the given test script to the to-test list. public void addTestScript(String filename){ listInFiles.add(filename); @@ -36,13 +56,22 @@ public class SQLTester { public void runTests() throws Exception { // process each input file for(String f : listInFiles){ + this.reset(); final TestScript ts = new TestScript(f); ts.setVerbose(this.outer.getVerbose()); verbose("Test",ts.getName(),"..."); - ts.dump(); + ts.run(this); } } + void resetInputBuffer(){ + this.inputBuffer.delete(0, this.inputBuffer.length()); + } + + void reset(){ + this.resetInputBuffer(); + } + public static void main(String[] argv) throws Exception{ final SQLTester t = new SQLTester(); for(String a : argv){ @@ -61,3 +90,43 @@ public class SQLTester { t.runTests(); } } + +abstract class Command { + private SQLTester tester; + Command(SQLTester t){tester = t;} + public SQLTester getTester(){return tester;} + + public abstract void process(String[] argv, String content); +} + +class NullCommand extends Command { + public NullCommand(SQLTester t){super(t);} + + public void process(String[] argv, String content){ + } +} + +class TestCaseCommand extends Command { + public TestCaseCommand(SQLTester t){super(t);} + + public void process(String[] argv, String content){ + } +} + +class ResultCommand extends Command { + public ResultCommand(SQLTester t){super(t);} + + public void process(String[] argv, String content){ + } +} + +class CommandFactory { + static Command getCommandByName(SQLTester t, String name){ + switch(name){ + case "null": return new NullCommand(t); + case "result": return new ResultCommand(t); + case "testcase": return new TestCaseCommand(t); + default: return null; + } + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index b4f8ec28be..6948229084 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -1,3 +1,16 @@ +/* +** 2023-08-08 +** +** 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 the TestScript part of the SQLTester framework. +*/ package org.sqlite.jni.tester; import java.util.List; import java.util.ArrayList; @@ -116,15 +129,30 @@ public class TestScript { final Matcher m = p.matcher(tmp); int ndxPrev = 0, pos = 0; String chunk; + int i = 0; + //verbose("Trimmed content:").verbose(tmp).verbose(""); while( m.find() ){ pos = m.start(); chunk = tmp.substring(ndxPrev, pos).trim(); - if( !chunk.isEmpty() ) rc.add( chunk ); + if( 0==ndxPrev && pos>ndxPrev ){ + /* Initial chunk of non-command state. Skip it. */ + ndxPrev = pos + 2; + continue; + } + if( !chunk.isEmpty() ){ + ++i; + //verbose("CHUNK #"+i,""+ndxPrev,"..",""+pos,chunk); + rc.add( chunk ); + } ndxPrev = pos + 2; } - if( ndxPrev != pos + 2 ){ + if( ndxPrev < tmp.length() ){ chunk = tmp.substring(ndxPrev, tmp.length()).trim(); - if( !chunk.isEmpty() ) rc.add( chunk ); + if( !chunk.isEmpty() ){ + ++i; + //verbose("CHUNK #"+(++i),chunk); + rc.add( chunk ); + } } return rc; } @@ -133,16 +161,30 @@ public class TestScript { A debug-only function which dumps the content of the test script in some form or other (possibly mangled from its original). */ - public void dump(){ + public void run(SQLTester tester) throws Exception { if( null==chunks ){ verbose("This contains content which forces it to be ignored."); }else{ verbose("script commands:"); int n = 0; - for(String c : chunks){ - verbose("#"+(++n),c); + for(String chunk : chunks){ + ++n; + //verbose("#"+n,c).verbose(""); + String[] parts = chunk.split("\\n", 2); + String[] argv = parts[0].split("\\s+"); + Command cmd = CommandFactory.getCommandByName(tester, argv[0]); + verbose("Command #"+n,argv[0],":", + (null==cmd ? "null" : cmd.getClass().getName())); + if(null == cmd){ + throw new IllegalArgumentException( + "No command handler found for '"+argv[0]+"'" + ); + } + if( parts.length > 1 ){ + verbose(parts[1]).verbose(""); + } + cmd.process(argv, parts.length>1 ? parts[1] : null); } - verbose(""); } } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 0037ef3ec3..b6744e74a7 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -2,6 +2,8 @@ # this line is ignored +junk + --null NULL --- also ignored --testcase first diff --git a/manifest b/manifest index d21ad98114..b453204c18 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C SQLTester\snow\signores\stests\swhich\scontain\sconstructs\sspecified\sin\sthe\sspec\sdoc. -D 2023-08-07T23:04:17.593 +C Add\scommand\sdispatcher\sto\sSQLTester. +D 2023-08-07T23:59:08.259 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -265,10 +265,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 8931ff9f152d22a822ff98831a4e924da48016ff1f1f84042390a6f51ad7b48f -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ee4ef9c65c9eedf6bfd68ad807346592ee01faf7fad16c3f18c462ed4c98c38 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 374bdad5cd0597aa9e20e5b8abd077a41d92caae6190ce383c250cb08859e7e4 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e717d2117f9589f17af599e527c126733b13a2cfded3f47297ae37c02e62e9ab +F ext/jni/src/org/sqlite/jni/tester/TestScript.java fa93b7abe11903412464b6cfbb6a2c44c738d4da291748ec99b28d3964bce846 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3 -F ext/jni/src/tests/000_first.test a06b72b6815246a21f6a4b14126bbc40b9cd1e3b03410431ed50203cfa942e9b +F ext/jni/src/tests/000_first.test dad04b8e386bb5a33d02c9647a20229daccea9889ead58c70a723d5d179facea F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d3d1accc8b4ba0cd396ee3a58d9710a54b8e1d1b171d67595d4ef1fc7faea8cb -R d5c9761c7876583110d4d0a8a0b413b4 +P ecaeee652aa2cc6893ded9231d7e9b2783465516016740b307b74e4e81598ae3 +R 248f12cc342cf395644ad37d3af803a2 U stephan -Z e82e467651a46849ab5637959c044f1b +Z 328e43fc1d1558590d24a0a8fa1662fc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b3ed60e6df..7c5d5bec87 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ecaeee652aa2cc6893ded9231d7e9b2783465516016740b307b74e4e81598ae3 \ No newline at end of file +e0a06931e91459ea43fed2954568bfafa7ad6b794fcff66e0d3bf0ed181db386 \ No newline at end of file From ec8edf329513f259d5bc9fafa2768e48235fcc3f Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 00:37:31 +0000 Subject: [PATCH 079/148] Rework SQLTester dispatching and add stub impls for several commmands. FossilOrigin-Name: 9e61af75ac83e74487a6ae681ee3ff891d8cf1f1d23bf895e9e3963ddf6eaf28 --- .../src/org/sqlite/jni/tester/SQLTester.java | 102 ++++++++++++++---- .../src/org/sqlite/jni/tester/TestScript.java | 20 +--- ext/jni/src/tests/000_first.test | 3 +- manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 99 insertions(+), 44 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 9ff5bc1ff4..441383ca0a 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -29,6 +29,7 @@ public class SQLTester { private final java.util.List listInFiles = new ArrayList<>(); private final Outer outer = new Outer(); private final StringBuilder inputBuffer = new StringBuilder(); + private String nullView = "nil"; public SQLTester(){ } @@ -68,10 +69,22 @@ public class SQLTester { this.inputBuffer.delete(0, this.inputBuffer.length()); } + String getInputBuffer(){ + return inputBuffer.toString(); + } + + String takeInputBuffer(){ + final String rc = inputBuffer.toString(); + resetInputBuffer(); + return rc; + } + void reset(){ this.resetInputBuffer(); } + void setNullValue(String v){nullView = v;} + public static void main(String[] argv) throws Exception{ final SQLTester t = new SQLTester(); for(String a : argv){ @@ -91,42 +104,93 @@ public class SQLTester { } } -abstract class Command { - private SQLTester tester; +class Command { + protected SQLTester tester; Command(SQLTester t){tester = t;} - public SQLTester getTester(){return tester;} - public abstract void process(String[] argv, String content); -} + protected final void badArg(String... msg){ + StringBuilder sb = new StringBuilder(); + int i = 0; + for(String s : msg) sb.append(((0==i++) ? "" : " ")+s); + throw new IllegalArgumentException(sb.toString()); + } -class NullCommand extends Command { - public NullCommand(SQLTester t){super(t);} + protected final void argcCheck(String[] argv, int min, int max){ + int argc = argv.length-1; + if(argcmax){ + if( min==max ) badArg(argv[0],"requires exactly",""+min,"argument(s)"); + else badArg(argv[0],"requires",""+min,"-",""+max,"arguments."); + } + } - public void process(String[] argv, String content){ + protected final void argcCheck(String[] argv, int argc){ + argcCheck(argv, argc, argc); + } + + protected void affirmNoContent(String content){ + if(null != content){ + badArg(this.getClass().getName(),"does not accept content."); + } } } -class TestCaseCommand extends Command { - public TestCaseCommand(SQLTester t){super(t);} +class DbCommand extends Command { + public DbCommand(SQLTester t, String[] argv, String content){ + super(t); + argcCheck(argv,1); + affirmNoContent(content); + //t.verbose(argv[0],argv[1]); + } +} - public void process(String[] argv, String content){ +class NullCommand extends Command { + public NullCommand(SQLTester t, String[] argv, String content){ + super(t); + argcCheck(argv,1); + affirmNoContent(content); + tester.setNullValue(argv[1]); + //t.verbose(argv[0],argv[1]); } } class ResultCommand extends Command { - public ResultCommand(SQLTester t){super(t);} - - public void process(String[] argv, String content){ + public ResultCommand(SQLTester t, String[] argv, String content){ + super(t); + argcCheck(argv,0); + t.verbose(argv[0],"command is TODO"); } } -class CommandFactory { - static Command getCommandByName(SQLTester t, String name){ +class TestCaseCommand extends Command { + public TestCaseCommand(SQLTester t, String[] argv, String content){ + super(t); + argcCheck(argv,1); + t.verbose(argv[0],argv[1]); + } +} + +class CommandDispatcher { + static Class getCommandByName(String name){ switch(name){ - case "null": return new NullCommand(t); - case "result": return new ResultCommand(t); - case "testcase": return new TestCaseCommand(t); + case "db": return DbCommand.class; + case "null": return NullCommand.class; + case "result": return ResultCommand.class; + case "testcase": return TestCaseCommand.class; default: return null; } } + + @SuppressWarnings("unchecked") + static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{ + final Class cmdClass = getCommandByName(argv[0]); + if(null == cmdClass){ + throw new IllegalArgumentException( + "No command handler found for '"+argv[0]+"'" + ); + } + final java.lang.reflect.Constructor ctor = + cmdClass.getConstructor(SQLTester.class, String[].class, String.class); + tester.verbose("Running",cmdClass.getSimpleName(),"..."); + ctor.newInstance(tester, argv, content); + } } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 6948229084..c94e348880 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -165,25 +165,15 @@ public class TestScript { if( null==chunks ){ verbose("This contains content which forces it to be ignored."); }else{ - verbose("script commands:"); int n = 0; for(String chunk : chunks){ ++n; //verbose("#"+n,c).verbose(""); - String[] parts = chunk.split("\\n", 2); - String[] argv = parts[0].split("\\s+"); - Command cmd = CommandFactory.getCommandByName(tester, argv[0]); - verbose("Command #"+n,argv[0],":", - (null==cmd ? "null" : cmd.getClass().getName())); - if(null == cmd){ - throw new IllegalArgumentException( - "No command handler found for '"+argv[0]+"'" - ); - } - if( parts.length > 1 ){ - verbose(parts[1]).verbose(""); - } - cmd.process(argv, parts.length>1 ? parts[1] : null); + final String[] parts = chunk.split("\\n", 2); + final String[] argv = parts[0].split("\\s+"); + CommandDispatcher.dispatch( + tester, argv, parts.length>1 ? parts[1] : null + ); } } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index b6744e74a7..58ad6e7013 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -4,7 +4,8 @@ junk ---null NULL +--null zilch +--db 1 --- also ignored --testcase first input for the first diff --git a/manifest b/manifest index b453204c18..c4e0df940e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\scommand\sdispatcher\sto\sSQLTester. -D 2023-08-07T23:59:08.259 +C Rework\sSQLTester\sdispatching\sand\sadd\sstub\simpls\sfor\sseveral\scommmands. +D 2023-08-08T00:37:31.945 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -265,10 +265,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 8931ff9f152d22a822ff98831a4e924da48016ff1f1f84042390a6f51ad7b48f -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e717d2117f9589f17af599e527c126733b13a2cfded3f47297ae37c02e62e9ab -F ext/jni/src/org/sqlite/jni/tester/TestScript.java fa93b7abe11903412464b6cfbb6a2c44c738d4da291748ec99b28d3964bce846 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java c4cadc7d0a8c86a9369a55527d711558206f8d2cf0ee8e26c1c1167a247e9c16 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java ed3cbc0371d0949293d9cc1925ce79d0d354fbe5282042dfbfe60c6e9d792fcd F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3 -F ext/jni/src/tests/000_first.test dad04b8e386bb5a33d02c9647a20229daccea9889ead58c70a723d5d179facea +F ext/jni/src/tests/000_first.test 29cc4ab0fa97a296ba0e2a7d821f7869503eec52e0fdb9de6a536dd02dd01946 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ecaeee652aa2cc6893ded9231d7e9b2783465516016740b307b74e4e81598ae3 -R 248f12cc342cf395644ad37d3af803a2 +P e0a06931e91459ea43fed2954568bfafa7ad6b794fcff66e0d3bf0ed181db386 +R 6790dfdd79e39541015901c80293f883 U stephan -Z 328e43fc1d1558590d24a0a8fa1662fc +Z 7dd3c312d5e23f2927529b8054c01871 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7c5d5bec87..96ab7475d9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e0a06931e91459ea43fed2954568bfafa7ad6b794fcff66e0d3bf0ed181db386 \ No newline at end of file +9e61af75ac83e74487a6ae681ee3ff891d8cf1f1d23bf895e9e3963ddf6eaf28 \ No newline at end of file From 58eebdaa25b9f29f70638ca24b0c367ef5e3a5bb Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 00:59:40 +0000 Subject: [PATCH 080/148] Add missing license header. Minor cleanups in SQLTester. FossilOrigin-Name: 5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2 --- ext/jni/src/org/sqlite/jni/tester/Outer.java | 27 +++++++++---- .../src/org/sqlite/jni/tester/SQLTester.java | 40 ++++++++++++------- .../src/org/sqlite/jni/tester/TestScript.java | 22 ++++------ ext/jni/src/tests/000_first.test | 2 + manifest | 18 ++++----- manifest.uuid | 2 +- 6 files changed, 66 insertions(+), 45 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java index f099fe3660..779d71f1e2 100644 --- a/ext/jni/src/org/sqlite/jni/tester/Outer.java +++ b/ext/jni/src/org/sqlite/jni/tester/Outer.java @@ -1,30 +1,43 @@ +/* +** 2023-08-08 +** +** 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 utility class for generating console output. +*/ package org.sqlite.jni.tester; -public class Outer { +class Outer { public boolean isVerbose = true; - public static void out(T val){ + public static void out(Object val){ System.out.print(val); } - public static void outln(T val){ + public static void outln(Object val){ System.out.println(val); } @SuppressWarnings("unchecked") - public static void out(T... vals){ + public static void out(Object... vals){ int n = 0; - for(T v : vals) out((n++>0 ? " " : "")+v); + for(Object v : vals) out((n++>0 ? " " : "")+v); } @SuppressWarnings("unchecked") - public static void outln(T... vals){ + public static void outln(Object... vals){ out(vals); out("\n"); } @SuppressWarnings("unchecked") - public Outer verbose(T... vals){ + public Outer verbose(Object... vals){ if(isVerbose) outln(vals); return this; } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 441383ca0a..a1ede71840 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -29,9 +29,12 @@ public class SQLTester { private final java.util.List listInFiles = new ArrayList<>(); private final Outer outer = new Outer(); private final StringBuilder inputBuffer = new StringBuilder(); - private String nullView = "nil"; + private String nullView; + private int totalTestCount = 0; + private int testCount; public SQLTester(){ + reset(); } public void setVerbose(boolean b){ @@ -39,13 +42,13 @@ public class SQLTester { } @SuppressWarnings("unchecked") - public void verbose(T... vals){ - this.outer.verbose(vals); + public void verbose(Object... vals){ + outer.verbose(vals); } @SuppressWarnings("unchecked") - public void outln(T... vals){ - this.outer.outln(vals); + public void outln(Object... vals){ + outer.outln(vals); } //! Adds the given test script to the to-test list. @@ -57,16 +60,17 @@ public class SQLTester { public void runTests() throws Exception { // process each input file for(String f : listInFiles){ - this.reset(); + reset(); final TestScript ts = new TestScript(f); ts.setVerbose(this.outer.getVerbose()); verbose("Test",ts.getName(),"..."); ts.run(this); + verbose("Ran",testCount,"test(s)."); } } void resetInputBuffer(){ - this.inputBuffer.delete(0, this.inputBuffer.length()); + inputBuffer.delete(0, this.inputBuffer.length()); } String getInputBuffer(){ @@ -80,11 +84,18 @@ public class SQLTester { } void reset(){ - this.resetInputBuffer(); + testCount = 0; + nullView = "nil"; + resetInputBuffer(); } void setNullValue(String v){nullView = v;} + void incrementTestCounter(){ + ++testCount; + ++totalTestCount; + } + public static void main(String[] argv) throws Exception{ final SQLTester t = new SQLTester(); for(String a : argv){ @@ -108,18 +119,18 @@ class Command { protected SQLTester tester; Command(SQLTester t){tester = t;} - protected final void badArg(String... msg){ + protected final void badArg(Object... msg){ StringBuilder sb = new StringBuilder(); int i = 0; - for(String s : msg) sb.append(((0==i++) ? "" : " ")+s); + for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s); throw new IllegalArgumentException(sb.toString()); } protected final void argcCheck(String[] argv, int min, int max){ int argc = argv.length-1; if(argcmax){ - if( min==max ) badArg(argv[0],"requires exactly",""+min,"argument(s)"); - else badArg(argv[0],"requires",""+min,"-",""+max,"arguments."); + if( min==max ) badArg(argv[0],"requires exactly",min,"argument(s)"); + else badArg(argv[0],"requires",min,"-",max,"arguments."); } } @@ -148,7 +159,7 @@ class NullCommand extends Command { super(t); argcCheck(argv,1); affirmNoContent(content); - tester.setNullValue(argv[1]); + t.setNullValue(argv[1]); //t.verbose(argv[0],argv[1]); } } @@ -158,6 +169,7 @@ class ResultCommand extends Command { super(t); argcCheck(argv,0); t.verbose(argv[0],"command is TODO"); + t.incrementTestCounter(); } } @@ -190,7 +202,7 @@ class CommandDispatcher { } final java.lang.reflect.Constructor ctor = cmdClass.getConstructor(SQLTester.class, String[].class, String.class); - tester.verbose("Running",cmdClass.getSimpleName(),"..."); + //tester.verbose("Running",argv[0],"..."); ctor.newInstance(tester, argv, content); } } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index c94e348880..c27528a3ae 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -16,8 +16,6 @@ import java.util.List; import java.util.ArrayList; import java.io.*; import java.util.regex.*; -//import java.util.List; -//import java.util.ArrayList; /** This class represents a single test script. It handles (or delegates) @@ -25,8 +23,7 @@ import java.util.regex.*; as-yet-non-existent, classes. */ -public class TestScript { - //! Test script content. +class TestScript { private String name; private String content; private List chunks = null; @@ -44,6 +41,7 @@ public class TestScript { } /** Initializes the script with the content of the given file. + Throws if it cannot read the file or if tokenizing it fails. */ public TestScript(String filename) throws Exception{ setContent(new String(readFile(filename), @@ -91,8 +89,8 @@ public class TestScript { } /** - A quick-and-dirty approach to chopping a script up into individual - commands and their inputs. + Chop script up into chunks containing individual commands and + their inputs. */ private List chunkContent(){ if( ignored ) return null; @@ -121,15 +119,11 @@ public class TestScript { tmp = m.replaceAll(""); } // Chunk the newly-cleaned text into individual commands and their input... - final String sCommand = "^--"; final List rc = new ArrayList<>(); - final Pattern p = Pattern.compile( - sCommand, Pattern.MULTILINE - ); + final Pattern p = Pattern.compile("^--", Pattern.MULTILINE); final Matcher m = p.matcher(tmp); - int ndxPrev = 0, pos = 0; + int ndxPrev = 0, pos = 0, i = 0; String chunk; - int i = 0; //verbose("Trimmed content:").verbose(tmp).verbose(""); while( m.find() ){ pos = m.start(); @@ -147,6 +141,7 @@ public class TestScript { ndxPrev = pos + 2; } if( ndxPrev < tmp.length() ){ + // This all belongs to the final command chunk = tmp.substring(ndxPrev, tmp.length()).trim(); if( !chunk.isEmpty() ){ ++i; @@ -158,8 +153,7 @@ public class TestScript { } /** - A debug-only function which dumps the content of the test script - in some form or other (possibly mangled from its original). + Runs this test script in the context of the given tester object. */ public void run(SQLTester tester) throws Exception { if( null==chunks ){ diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 58ad6e7013..aaec5e08b5 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -10,6 +10,8 @@ junk --testcase first input for the first command; +--result +hello world --testcase second select 1 --result /* ignored */ diff --git a/manifest b/manifest index c4e0df940e..8999d330f1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Rework\sSQLTester\sdispatching\sand\sadd\sstub\simpls\sfor\sseveral\scommmands. -D 2023-08-08T00:37:31.945 +C Add\smissing\slicense\sheader.\sMinor\scleanups\sin\sSQLTester. +D 2023-08-08T00:59:40.520 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -264,11 +264,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java ff3729426704626a6019d97bfee512a83f253c F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/Outer.java 8931ff9f152d22a822ff98831a4e924da48016ff1f1f84042390a6f51ad7b48f -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java c4cadc7d0a8c86a9369a55527d711558206f8d2cf0ee8e26c1c1167a247e9c16 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java ed3cbc0371d0949293d9cc1925ce79d0d354fbe5282042dfbfe60c6e9d792fcd +F ext/jni/src/org/sqlite/jni/tester/Outer.java c35a54bd3fd3363ba2abb5533453454d8ffe3f942c9a37a7921c8f6739762e82 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d89dc9921b41aea67ee3c69e763671467042b33b62085aa7a44444856c4eca3f +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 6a631e2ecce24734bd93631ce00446ed109aaeb1ea6666f7a8aff74d96221a6a F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3 -F ext/jni/src/tests/000_first.test 29cc4ab0fa97a296ba0e2a7d821f7869503eec52e0fdb9de6a536dd02dd01946 +F ext/jni/src/tests/000_first.test 0a066e5e30189545ca4f3586d45db6c08195a50bd2f00907b4d6a3727ff58c02 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P e0a06931e91459ea43fed2954568bfafa7ad6b794fcff66e0d3bf0ed181db386 -R 6790dfdd79e39541015901c80293f883 +P 9e61af75ac83e74487a6ae681ee3ff891d8cf1f1d23bf895e9e3963ddf6eaf28 +R 75e2a8b50272267a155f3645b409db9d U stephan -Z 7dd3c312d5e23f2927529b8054c01871 +Z fbbe7908dd24ba8c5b8dfecd37f39fe7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 96ab7475d9..fa8b36a1f2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -9e61af75ac83e74487a6ae681ee3ff891d8cf1f1d23bf895e9e3963ddf6eaf28 \ No newline at end of file +5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2 \ No newline at end of file From aeae7904a54273c0e5c9e475e681daef0067b0db Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 09:45:33 +0000 Subject: [PATCH 081/148] SQLTester: add print command and improve argument error reporting infrastructure. FossilOrigin-Name: 1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44 --- ext/jni/src/org/sqlite/jni/Tester1.java | 19 +++- .../src/org/sqlite/jni/tester/SQLTester.java | 101 +++++++++++++----- .../src/org/sqlite/jni/tester/TestScript.java | 14 ++- ext/jni/src/tests/000_first.test | 2 + manifest | 18 ++-- manifest.uuid | 2 +- 6 files changed, 117 insertions(+), 39 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index b255910614..57df78e9f6 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -25,14 +25,25 @@ public class Tester1 { private static final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); - public static void out(T val){ + public static void out(Object val){ System.out.print(val); } - public static void outln(T val){ + public static void outln(Object val){ System.out.println(val); } + @SuppressWarnings("unchecked") + public static void out(Object... vals){ + int n = 0; + for(Object v : vals) out((n++>0 ? " " : "")+v); + } + + @SuppressWarnings("unchecked") + public static void outln(Object... vals){ + out(vals); out("\n"); + } + static int affirmCount = 0; public static void affirm(Boolean v){ ++affirmCount; @@ -42,8 +53,8 @@ public class Tester1 { } private static void test1(){ - outln("libversion_number: " - + sqlite3_libversion_number() + outln("libversion_number:", + sqlite3_libversion_number() + "\n" + sqlite3_libversion() + "\n" diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index a1ede71840..8dcd88c7d7 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -15,6 +15,7 @@ package org.sqlite.jni.tester; import java.util.List; import java.util.ArrayList; +import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; /** @@ -30,8 +31,10 @@ public class SQLTester { private final Outer outer = new Outer(); private final StringBuilder inputBuffer = new StringBuilder(); private String nullView; - private int totalTestCount = 0; - private int testCount; + private int nTotalTest = 0; + private int nTestFile = 0; + private int nTest; + private sqlite3[] aDb = {}; public SQLTester(){ reset(); @@ -51,6 +54,11 @@ public class SQLTester { outer.outln(vals); } + @SuppressWarnings("unchecked") + public void out(Object... vals){ + outer.out(vals); + } + //! Adds the given test script to the to-test list. public void addTestScript(String filename){ listInFiles.add(filename); @@ -61,19 +69,29 @@ public class SQLTester { // process each input file for(String f : listInFiles){ reset(); + ++nTestFile; final TestScript ts = new TestScript(f); ts.setVerbose(this.outer.getVerbose()); - verbose("Test",ts.getName(),"..."); + verbose(">>> Test",ts.getName(),"..."); ts.run(this); - verbose("Ran",testCount,"test(s)."); + verbose("<<< Ran",nTest,"test(s) in",f); } } - void resetInputBuffer(){ - inputBuffer.delete(0, this.inputBuffer.length()); + private void resetDbs(){ + for(sqlite3 db : aDb) sqlite3_close_v2(db); } - String getInputBuffer(){ + StringBuilder resetInputBuffer(){ + inputBuffer.delete(0, inputBuffer.length()); + return inputBuffer; + } + + StringBuilder getInputBuffer(){ + return inputBuffer; + } + + String getInputBufferText(){ return inputBuffer.toString(); } @@ -84,17 +102,15 @@ public class SQLTester { } void reset(){ - testCount = 0; + nTest = 0; nullView = "nil"; resetInputBuffer(); + resetDbs(); } void setNullValue(String v){nullView = v;} - void incrementTestCounter(){ - ++testCount; - ++totalTestCount; - } + void incrementTestCounter(){ ++nTest; ++nTotalTest; } public static void main(String[] argv) throws Exception{ final SQLTester t = new SQLTester(); @@ -112,21 +128,49 @@ public class SQLTester { t.addTestScript(a); } t.runTests(); + t.outer.outln("Processed",t.nTotalTest,"test(s) in",t.nTestFile,"file(s)."); } } +/** + Base class for test script commands. + + Each subclass must have a ctor with this signature: + + (SQLTester testContext, String[] argv, String content) throws Exception + + argv is a list with the command name followed by any + arguments to that command. The argcCheck() method provides + very basic argc validation. + + The content is any text content which was specified after the + command. Any command which does not permit content must pass that + argument to affirmNoContent() in their constructor. + + Tests must throw on error. +*/ class Command { protected SQLTester tester; Command(SQLTester t){tester = t;} - protected final void badArg(Object... msg){ + protected final void toss(Class errorType, Object... msg) throws Exception { StringBuilder sb = new StringBuilder(); int i = 0; for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s); - throw new IllegalArgumentException(sb.toString()); + final java.lang.reflect.Constructor ctor = + errorType.getConstructor(String.class); + throw ctor.newInstance(sb.toString()); } - protected final void argcCheck(String[] argv, int min, int max){ + protected final void toss(Object... msg) throws Exception{ + toss(RuntimeException.class, msg); + } + + protected final void badArg(Object... msg) throws Exception{ + toss(IllegalArgumentException.class, msg); + } + + protected final void argcCheck(String[] argv, int min, int max) throws Exception{ int argc = argv.length-1; if(argcmax){ if( min==max ) badArg(argv[0],"requires exactly",min,"argument(s)"); @@ -134,11 +178,11 @@ class Command { } } - protected final void argcCheck(String[] argv, int argc){ + protected final void argcCheck(String[] argv, int argc) throws Exception{ argcCheck(argv, argc, argc); } - protected void affirmNoContent(String content){ + protected void affirmNoContent(String content) throws Exception{ if(null != content){ badArg(this.getClass().getName(),"does not accept content."); } @@ -146,7 +190,7 @@ class Command { } class DbCommand extends Command { - public DbCommand(SQLTester t, String[] argv, String content){ + public DbCommand(SQLTester t, String[] argv, String content) throws Exception{ super(t); argcCheck(argv,1); affirmNoContent(content); @@ -155,7 +199,7 @@ class DbCommand extends Command { } class NullCommand extends Command { - public NullCommand(SQLTester t, String[] argv, String content){ + public NullCommand(SQLTester t, String[] argv, String content) throws Exception{ super(t); argcCheck(argv,1); affirmNoContent(content); @@ -164,20 +208,28 @@ class NullCommand extends Command { } } -class ResultCommand extends Command { - public ResultCommand(SQLTester t, String[] argv, String content){ +class PrintCommand extends Command { + public PrintCommand(SQLTester t, String[] argv, String content) throws Exception{ super(t); argcCheck(argv,0); - t.verbose(argv[0],"command is TODO"); + t.outln(content); + } +} + +class ResultCommand extends Command { + public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{ + super(t); + argcCheck(argv,0); + //t.verbose(argv[0],"command is TODO"); t.incrementTestCounter(); } } class TestCaseCommand extends Command { - public TestCaseCommand(SQLTester t, String[] argv, String content){ + public TestCaseCommand(SQLTester t, String[] argv, String content) throws Exception{ super(t); argcCheck(argv,1); - t.verbose(argv[0],argv[1]); + //t.verbose(argv[0],argv[1]); } } @@ -186,6 +238,7 @@ class CommandDispatcher { switch(name){ case "db": return DbCommand.class; case "null": return NullCommand.class; + case "print": return PrintCommand.class; case "result": return ResultCommand.class; case "testcase": return TestCaseCommand.class; default: return null; diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index c27528a3ae..c1e26e7a0a 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -90,7 +90,19 @@ class TestScript { /** Chop script up into chunks containing individual commands and - their inputs. + their inputs. The approach taken here is not as robust as + line-by-line parsing would be but the framework is structured + such that we could replace this part without unduly affecting the + evaluation bits. The potential problems with this approach + include: + + - It's potentially possible that it will strip content out of a + testcase block. + + - It loses all file location information, so we can't report line + numbers of errors. + + If/when that becomes a problem, it can be refactored. */ private List chunkContent(){ if( ignored ) return null; diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index aaec5e08b5..9b79afbe68 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -5,6 +5,8 @@ junk --null zilch +--print +This is from the print command. --db 1 --- also ignored --testcase first diff --git a/manifest b/manifest index 8999d330f1..ff02858a35 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\smissing\slicense\sheader.\sMinor\scleanups\sin\sSQLTester. -D 2023-08-08T00:59:40.520 +C SQLTester:\sadd\sprint\scommand\sand\simprove\sargument\serror\sreporting\sinfrastructure. +D 2023-08-08T09:45:33.280 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -252,7 +252,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21b828cb61984977c7b2a0cbd803f0c15dd79ff524ab94d3ce52648c011b2c9c -F ext/jni/src/org/sqlite/jni/Tester1.java 55bf9c35c4a5649bdfb6ce940117d33ec24a6722bc252fadf7bc7102b9e94d6a +F ext/jni/src/org/sqlite/jni/Tester1.java 57404879fbea78f0b405b7643abb03dad0a6ce6cea9ec0c4ef55ea40267be565 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -265,10 +265,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java c35a54bd3fd3363ba2abb5533453454d8ffe3f942c9a37a7921c8f6739762e82 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d89dc9921b41aea67ee3c69e763671467042b33b62085aa7a44444856c4eca3f -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 6a631e2ecce24734bd93631ce00446ed109aaeb1ea6666f7a8aff74d96221a6a +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1832399b73a1e246892149cbfad0ca5b8cf1ed69072322059fa9a14b2da2b1f1 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 38652e01cab9c07b20741829f54ef2f4a5c25a73b2c77213dd9198d4268acc51 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3 -F ext/jni/src/tests/000_first.test 0a066e5e30189545ca4f3586d45db6c08195a50bd2f00907b4d6a3727ff58c02 +F ext/jni/src/tests/000_first.test 00b2347d4b974e67682859c292bc0d200788ab3f462eac922b8036f4e07114fc F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 9e61af75ac83e74487a6ae681ee3ff891d8cf1f1d23bf895e9e3963ddf6eaf28 -R 75e2a8b50272267a155f3645b409db9d +P 5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2 +R 8128e06f40a69e4ebe18ddcc9febcc5a U stephan -Z fbbe7908dd24ba8c5b8dfecd37f39fe7 +Z d82fef86e57475799cbc6287645847ba # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index fa8b36a1f2..4a70ce0c93 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2 \ No newline at end of file +1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44 \ No newline at end of file From c693bd59f768e3b78cc36eeb9268042b40f72ac7 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 10:58:16 +0000 Subject: [PATCH 082/148] Bind sqlite3_strlike/strglob() to JNI. FossilOrigin-Name: eb5440f71be32812f6310756b8e30958002e8e8e41a7eb16f081058ff733b47c --- ext/jni/src/c/sqlite3-jni.c | 30 +++++++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 16 ++++++++++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 18 +++++++++++++ manifest | 16 ++++++------ manifest.uuid | 2 +- 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 1c17ed970e..7d4c4f8580 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -543,6 +543,11 @@ static struct { #endif } S3JniGlobal; +#define OOM_CHECK(VAR) if(!(VAR)) s3jni_oom(env) +static void s3jni_oom(JNIEnv * const env){ + (*env)->FatalError(env, "Out of memory.") /* does not return */; +} + /** sqlite3_malloc() proxy which fails fatally on OOM. This should only be used for routines which manage global state and have no @@ -551,9 +556,7 @@ static struct { */ static void * s3jni_malloc(JNIEnv * const env, size_t n){ void * const rv = sqlite3_malloc(n); - if(n && !rv){ - (*env)->FatalError(env, "Out of memory.") /* does not return */; - } + if(n && !rv) s3jni_oom(env); return rv; } @@ -3056,6 +3059,27 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ (sqlite3_int64)rowId); } +static int s3jni_strlike_glob(int isLike, JNIEnv *const env, + jbyteArray baG, jbyteArray baT){ + int rc = 0; + jbyte * const pG = JBA_TOC(baG); + jbyte * const pT = pG ? JBA_TOC(baT) : 0; + + OOM_CHECK(pT); + rc = sqlite3_strglob((const char *)pG, (const char *)pT); + JBA_RELEASE(baG, pG); + JBA_RELEASE(baT, pT); + return rc; +} + +JDECL(int,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ + return s3jni_strlike_glob(0, env, baG, baT); +} + +JDECL(int,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ + return s3jni_strlike_glob(1, env, baG, baT); +} + JDECL(jint,1shutdown)(JENV_CSELF){ S3JniGlobal_S3JniEnvCache_clear(); /* Do not clear S3JniGlobal.jvm: it's legal to call diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 3d52b9a75e..ea9b47e68f 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1459,6 +1459,22 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1step (JNIEnv *, jclass, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_strglob + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_strlike + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strlike + (JNIEnv *, jclass, jbyteArray, jbyteArray); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_threadsafe diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 647989230f..63fb08ccca 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -850,6 +850,24 @@ public final class SQLite3Jni { public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + private static native int sqlite3_strglob(@NotNull byte[] glob, @NotNull byte[] txt); + + public static int sqlite3_strglob(@NotNull String glob, @NotNull String txt){ + return sqlite3_strglob( + glob.getBytes(StandardCharsets.UTF_8), + txt.getBytes(StandardCharsets.UTF_8) + ); + } + + private static native int sqlite3_strlike(@NotNull byte[] glob, @NotNull byte[] txt); + + public static int sqlite3_strlike(@NotNull String glob, @NotNull String txt){ + return sqlite3_strlike( + glob.getBytes(StandardCharsets.UTF_8), + txt.getBytes(StandardCharsets.UTF_8) + ); + } + public static native int sqlite3_threadsafe(); public static native int sqlite3_total_changes(@NotNull sqlite3 db); diff --git a/manifest b/manifest index ff02858a35..8fcca18dea 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C SQLTester:\sadd\sprint\scommand\sand\simprove\sargument\serror\sreporting\sinfrastructure. -D 2023-08-08T09:45:33.280 +C Bind\ssqlite3_strlike/strglob()\sto\sJNI. +D 2023-08-08T10:58:16.862 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 2b800c74db98b64b63ec1da48dc4a27738f88951f0ca43011288abf80c1b5e80 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c e6463b3fc8ef000d9a5dd1649fe96a4cfc5aac21a43276424cf28d72548c5921 -F ext/jni/src/c/sqlite3-jni.h 6c06cdb1e43ce56544dfbe3335a46174dae15d03337d06e55aa7256f85d2ce1b +F ext/jni/src/c/sqlite3-jni.c eb105982266523f4cd9c5007c3cc713855aca520a0f24fce138e4ecfd573fc20 +F ext/jni/src/c/sqlite3-jni.h bc3ecd3f6e479fd45b80214f6256584cc599336ae222822fa1e603c22ff1fb19 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -251,7 +251,7 @@ F ext/jni/src/org/sqlite/jni/OutputPointer.java ebdd33d48064c3302d0d4a6dd345562a F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21b828cb61984977c7b2a0cbd803f0c15dd79ff524ab94d3ce52648c011b2c9c +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3681e6ea94973ce1f7facd6887853a4ae5657a9274dd06279b586dbf77f36c2d F ext/jni/src/org/sqlite/jni/Tester1.java 57404879fbea78f0b405b7643abb03dad0a6ce6cea9ec0c4ef55ea40267be565 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5be50fd5887e5378f7d66405bff3a44117a826a17e5f1a18c5129d1109ecdae2 -R 8128e06f40a69e4ebe18ddcc9febcc5a +P 1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44 +R 7802ddad819423a1071e483ed129b94a U stephan -Z d82fef86e57475799cbc6287645847ba +Z f01d533536a4e5fdaf6eae004d864cb3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4a70ce0c93..c38e7ddfe5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44 \ No newline at end of file +eb5440f71be32812f6310756b8e30958002e8e8e41a7eb16f081058ff733b47c \ No newline at end of file From ec9b37b33e31a9fbd7c5d516b03bb964f5557008 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 11:46:26 +0000 Subject: [PATCH 083/148] Add a JNI-layer sqlite3.toString() for debugging. FossilOrigin-Name: 456691649aa2a7672d5d110acdde92426a9d34552863db3e0c86b73d9c5d9aac --- ext/jni/src/org/sqlite/jni/sqlite3.java | 12 ++++++++++++ manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/sqlite3.java b/ext/jni/src/org/sqlite/jni/sqlite3.java index 8f4ecb772b..cfc6c08d47 100644 --- a/ext/jni/src/org/sqlite/jni/sqlite3.java +++ b/ext/jni/src/org/sqlite/jni/sqlite3.java @@ -22,4 +22,16 @@ package org.sqlite.jni; public final class sqlite3 extends NativePointerHolder { // Only invoked from JNI private sqlite3(){} + + public String toString(){ + long ptr = getNativePointer(); + if( 0==ptr ){ + return sqlite3.class.getSimpleName()+"@null"; + } + String fn = SQLite3Jni.sqlite3_db_filename(this, "main"); + return sqlite3.class.getSimpleName() + +"@"+String.format("0x%08x",ptr) + +"["+((null == fn) ? "" : fn)+"]" + ; + } } diff --git a/manifest b/manifest index 8fcca18dea..6f349e59dd 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_strlike/strglob()\sto\sJNI. -D 2023-08-08T10:58:16.862 +C Add\sa\sJNI-layer\ssqlite3.toString()\sfor\sdebugging. +D 2023-08-08T11:46:26.111 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -260,7 +260,7 @@ F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71 F ext/jni/src/org/sqlite/jni/fts5_api.java 8c6b32455d7f85ee3f7f3e71c148bb3c2106f1d5484017daddfd560dd69d4f66 F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b -F ext/jni/src/org/sqlite/jni/sqlite3.java ff3729426704626a6019d97bfee512a83f253cf43ffeffbd45b238718154df36 +F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 1b6e84f6aa5c7626a308b5e8efe5c3d83ec8e7eaa803f047576b7c65333c2d44 -R 7802ddad819423a1071e483ed129b94a +P eb5440f71be32812f6310756b8e30958002e8e8e41a7eb16f081058ff733b47c +R 4cc267c21faf6068f5badbc12783e171 U stephan -Z f01d533536a4e5fdaf6eae004d864cb3 +Z 92a4b7bac87478c8b69fd6008856678d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c38e7ddfe5..faff09de56 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eb5440f71be32812f6310756b8e30958002e8e8e41a7eb16f081058ff733b47c \ No newline at end of file +456691649aa2a7672d5d110acdde92426a9d34552863db3e0c86b73d9c5d9aac \ No newline at end of file From 405dffd591fbb57a9c9c666771e8a99666e23b97 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 11:46:46 +0000 Subject: [PATCH 084/148] Implement the new/open/close SQLTester commands. FossilOrigin-Name: dc823bf00f78e7cd626329220c42c46da12d565e3273a08eda5fb512c1d807c6 --- ext/jni/GNUmakefile | 4 +- ext/jni/src/org/sqlite/jni/tester/Outer.java | 13 +- .../src/org/sqlite/jni/tester/SQLTester.java | 268 ++++++++++++++---- .../jni/tester/test-script-interpreter.md | 10 +- ext/jni/src/tests/000_first.test | 10 +- manifest | 20 +- manifest.uuid | 2 +- 7 files changed, 249 insertions(+), 78 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 4e537f123b..66df526225 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -218,10 +218,12 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) .PHONY: tester +tester.flags ?= --verbose + tester: $(CLASS_FILES.tester) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ - org.sqlite.jni.tester.SQLTester $(tester.scripts) + org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts) $(package.jar): $(CLASS_FILES) $(MAKEFILE) rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java index 779d71f1e2..f86277d14d 100644 --- a/ext/jni/src/org/sqlite/jni/tester/Outer.java +++ b/ext/jni/src/org/sqlite/jni/tester/Outer.java @@ -13,8 +13,11 @@ */ package org.sqlite.jni.tester; +/** + Console output utility class. +*/ class Outer { - public boolean isVerbose = true; + public boolean verbose = false; public static void out(Object val){ System.out.print(val); @@ -38,16 +41,14 @@ class Outer { @SuppressWarnings("unchecked") public Outer verbose(Object... vals){ - if(isVerbose) outln(vals); + if(verbose) outln(vals); return this; } public void setVerbose(boolean b){ - isVerbose = b; + verbose = b; } - public boolean getVerbose(){ - return isVerbose; - } + public boolean isVerbose(){return verbose;} } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 8dcd88c7d7..5fad8a6c0b 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -28,13 +28,18 @@ import static org.sqlite.jni.SQLite3Jni.*; public class SQLTester { //! List of input script files. private final java.util.List listInFiles = new ArrayList<>(); + //! Console output utility. private final Outer outer = new Outer(); + //! Test input buffer. private final StringBuilder inputBuffer = new StringBuilder(); + //! Test result buffer. + private final StringBuilder resultBuffer = new StringBuilder(); private String nullView; private int nTotalTest = 0; private int nTestFile = 0; private int nTest; - private sqlite3[] aDb = {}; + private final sqlite3[] aDb = new sqlite3[7]; + private int iCurrentDb = 0; public SQLTester(){ reset(); @@ -67,45 +72,100 @@ public class SQLTester { public void runTests() throws Exception { // process each input file + outln("Verbose =",outer.isVerbose()); for(String f : listInFiles){ reset(); ++nTestFile; final TestScript ts = new TestScript(f); - ts.setVerbose(this.outer.getVerbose()); - verbose(">>> Test",ts.getName(),"..."); + outln("---------> Test",ts.getName(),"..."); ts.run(this); - verbose("<<< Ran",nTest,"test(s) in",f); + outln("<---------",nTest,"test(s) in",f); } } - private void resetDbs(){ - for(sqlite3 db : aDb) sqlite3_close_v2(db); + private StringBuilder resetBuffer(StringBuilder b){ + b.delete(0, b.length()); + return b; } StringBuilder resetInputBuffer(){ - inputBuffer.delete(0, inputBuffer.length()); - return inputBuffer; + return resetBuffer(inputBuffer); } - StringBuilder getInputBuffer(){ - return inputBuffer; + StringBuilder resetResultBuffer(){ + return resetBuffer(resultBuffer); } - String getInputBufferText(){ - return inputBuffer.toString(); - } + StringBuilder getInputBuffer(){ return inputBuffer; } - String takeInputBuffer(){ - final String rc = inputBuffer.toString(); - resetInputBuffer(); + String getInputBufferText(){ return inputBuffer.toString(); } + + private String takeBuffer(StringBuilder b){ + final String rc = b.toString(); + resetBuffer(b); return rc; } + String takeInputBuffer(){ return takeBuffer(inputBuffer); } + + String takeResultBuffer(){ return takeBuffer(resultBuffer); } + + int getCurrentDbId(){ return iCurrentDb; } + + SQLTester affirmDbId(int n) throws Exception{ + if(n<0 || n>=aDb.length){ + Util.toss(IllegalArgumentException.class,"illegal db number."); + } + return this; + } + + sqlite3 setCurrentDb(int n) throws Exception{ + return affirmDbId(n).aDb[n]; + } + + sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } + + void closeDb(int id) throws Exception{ + final sqlite3 db = affirmDbId(id).aDb[id]; + if( null != db ){ + sqlite3_close_v2(db); + aDb[id] = null; + } + } + + void closeDb() throws Exception { closeDb(iCurrentDb); } + + void closeAllDbs(){ + for(int i = 0; i errorType, Object... msg) throws Exception { - StringBuilder sb = new StringBuilder(); - int i = 0; - for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s); - final java.lang.reflect.Constructor ctor = - errorType.getConstructor(String.class); - throw ctor.newInstance(sb.toString()); - } - - protected final void toss(Object... msg) throws Exception{ - toss(RuntimeException.class, msg); - } - - protected final void badArg(Object... msg) throws Exception{ - toss(IllegalArgumentException.class, msg); - } + protected Command(){} protected final void argcCheck(String[] argv, int min, int max) throws Exception{ int argc = argv.length-1; if(argcmax){ - if( min==max ) badArg(argv[0],"requires exactly",min,"argument(s)"); - else badArg(argv[0],"requires",min,"-",max,"arguments."); + if( min==max ) Util.badArg(argv[0],"requires exactly",min,"argument(s)"); + else Util.badArg(argv[0],"requires",min,"-",max,"arguments."); } } @@ -182,25 +231,90 @@ class Command { argcCheck(argv, argc, argc); } + //! Throws if content is not null. protected void affirmNoContent(String content) throws Exception{ if(null != content){ - badArg(this.getClass().getName(),"does not accept content."); + Util.badArg(this.getClass().getName(),"does not accept content."); } } + + //! Throws if content is null. + protected void affirmHasContent(String content) throws Exception{ + if(null == content){ + Util.badArg(this.getClass().getName(),"requires content."); + } + } +} + +class CloseDbCommand extends Command { + public CloseDbCommand(SQLTester t, String[] argv, String content) throws Exception{ + argcCheck(argv,0,1); + affirmNoContent(content); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.verbose(argv[0],"all dbs"); + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + t.verbose(argv[0],"db",id); + } } class DbCommand extends Command { public DbCommand(SQLTester t, String[] argv, String content) throws Exception{ - super(t); argcCheck(argv,1); affirmNoContent(content); - //t.verbose(argv[0],argv[1]); + final sqlite3 db = t.setCurrentDb( Integer.parseInt(argv[1]) ); + t.verbose(argv[0],"set db to",db); + } +} + +class GlobCommand extends Command { + protected GlobCommand(boolean negate, SQLTester t, + String[] argv, String content) throws Exception{ + argcCheck(argv,1); + affirmNoContent(content); + final String glob = argv[1].replace("#","[0-9]"); + t.verbose(argv[0],"is TODO. Pattern =",glob); + } + public GlobCommand(SQLTester t, String[] argv, String content) throws Exception{ + this(false, t, argv, content); + } +} + +class NewDbCommand extends Command { + public NewDbCommand(SQLTester t, String[] argv, String content) throws Exception{ + argcCheck(argv,1); + affirmNoContent(content); + String fname = argv[1]; + Util.unlink(fname); + final sqlite3 db = t.openDb(fname, true); + t.verbose(argv[0],"db",db); + } +} + +class NoopCommand extends Command { + public NoopCommand(SQLTester t, String[] argv, String content) throws Exception{ + } +} + +class NotGlobCommand extends GlobCommand { + public NotGlobCommand(SQLTester t, String[] argv, String content) throws Exception{ + super(true, t, argv, content); } } class NullCommand extends Command { public NullCommand(SQLTester t, String[] argv, String content) throws Exception{ - super(t); argcCheck(argv,1); affirmNoContent(content); t.setNullValue(argv[1]); @@ -208,9 +322,20 @@ class NullCommand extends Command { } } +class OpenDbCommand extends Command { + public OpenDbCommand(SQLTester t, String[] argv, String content) throws Exception{ + argcCheck(argv,1); + affirmNoContent(content); + String fname = argv[1]; + Util.unlink(fname); + final sqlite3 db = t.openDb(fname, false); + t.verbose(argv[0],"db",db); + } +} + + class PrintCommand extends Command { public PrintCommand(SQLTester t, String[] argv, String content) throws Exception{ - super(t); argcCheck(argv,0); t.outln(content); } @@ -218,7 +343,6 @@ class PrintCommand extends Command { class ResultCommand extends Command { public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{ - super(t); argcCheck(argv,0); //t.verbose(argv[0],"command is TODO"); t.incrementTestCounter(); @@ -227,19 +351,28 @@ class ResultCommand extends Command { class TestCaseCommand extends Command { public TestCaseCommand(SQLTester t, String[] argv, String content) throws Exception{ - super(t); argcCheck(argv,1); - //t.verbose(argv[0],argv[1]); + affirmHasContent(content); + t.resetInputBuffer(); + t.resetResultBuffer().append(content); + t.verbose(argv[0],"result buffer:",content); } } class CommandDispatcher { + static Class getCommandByName(String name){ switch(name){ - case "db": return DbCommand.class; - case "null": return NullCommand.class; - case "print": return PrintCommand.class; - case "result": return ResultCommand.class; + case "close": return CloseDbCommand.class; + case "db": return DbCommand.class; + case "glob": return GlobCommand.class; + case "new": return NewDbCommand.class; + case "notglob": return NotGlobCommand.class; + case "null": return NullCommand.class; + case "oom": return NoopCommand.class; + case "open": return OpenDbCommand.class; + case "print": return PrintCommand.class; + case "result": return ResultCommand.class; case "testcase": return TestCaseCommand.class; default: return null; } @@ -259,3 +392,32 @@ class CommandDispatcher { ctor.newInstance(tester, argv, content); } } + +final class Util { + public static void toss(Class errorType, Object... msg) throws Exception { + StringBuilder sb = new StringBuilder(); + int i = 0; + for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s); + final java.lang.reflect.Constructor ctor = + errorType.getConstructor(String.class); + throw ctor.newInstance(sb.toString()); + } + + public static void toss(Object... msg) throws Exception{ + toss(RuntimeException.class, msg); + } + + public static void badArg(Object... msg) throws Exception{ + toss(IllegalArgumentException.class, msg); + } + + public static void unlink(String filename){ + try{ + final java.io.File f = new java.io.File(filename); + f.delete(); + }catch(Exception e){ + /* ignore */ + } + } + +} diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index d8610a68b8..998bbeadcc 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -170,10 +170,12 @@ which database connection to use moving forward. The --close command causes an existing database connetion to close. This command is a no-op if the database connection is not currently -open. There can be up to 7 different database connections, numbered -0 through 6. The number of the database connection to close is an argument -to the --close command. Or if the argument to --close is "all" then all -open database connections are closed. +open. There can be up to 7 different database connections, numbered 0 +through 6. The number of the database connection to close is an +argument to the --close command, which will fail if an out-of-range +value is provided. Or if the argument to --close is "all" then all +open database connections are closed. If passed no argument, the +currently-active database is assumed. ### The --null command diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 9b79afbe68..9321576245 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -4,10 +4,11 @@ junk +--new SQLTester.db --null zilch +--oom --print This is from the print command. ---db 1 --- also ignored --testcase first input for the first @@ -16,5 +17,8 @@ command; hello world --testcase second select 1 ---result /* ignored */ -1 +--glob # /* ignored */ +--testcase second +select 'a' +--notglob # +--close diff --git a/manifest b/manifest index 6f349e59dd..95c9daba02 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\sJNI-layer\ssqlite3.toString()\sfor\sdebugging. -D 2023-08-08T11:46:26.111 +C Implement\sthe\snew/open/close\sSQLTester\scommands. +D 2023-08-08T11:46:46.234 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,7 @@ 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 2b800c74db98b64b63ec1da48dc4a27738f88951f0ca43011288abf80c1b5e80 +F ext/jni/GNUmakefile 0d071597509ef4a9ac4b7712dac9ef29ded0db4819721c3b3c15e24d534827f6 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c eb105982266523f4cd9c5007c3cc713855aca520a0f24fce138e4ecfd573fc20 F ext/jni/src/c/sqlite3-jni.h bc3ecd3f6e479fd45b80214f6256584cc599336ae222822fa1e603c22ff1fb19 @@ -264,11 +264,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/Outer.java c35a54bd3fd3363ba2abb5533453454d8ffe3f942c9a37a7921c8f6739762e82 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1832399b73a1e246892149cbfad0ca5b8cf1ed69072322059fa9a14b2da2b1f1 +F ext/jni/src/org/sqlite/jni/tester/Outer.java 07b5d68bdc4a01173f2515954a250dce6affcc4efb85b1ac50d24ad05b166bf4 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2261be136c31a432a7416fa86810b97a28a2ad8012463ae68f0975ceac38b42f F ext/jni/src/org/sqlite/jni/tester/TestScript.java 38652e01cab9c07b20741829f54ef2f4a5c25a73b2c77213dd9198d4268acc51 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 2627f8ac7c3d3f502404d9a9b8481bad5c2d11df7fdf25fbd0e1dbd2bcaa54c3 -F ext/jni/src/tests/000_first.test 00b2347d4b974e67682859c292bc0d200788ab3f462eac922b8036f4e07114fc +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 99d2b8521af9362382ca57b19ae2f3f48db9909221aa9b20ae1a97255120760c +F ext/jni/src/tests/000_first.test f58d5f22e2db31b20c6e744a247d14222508c88ed876b03a723c89f540948518 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P eb5440f71be32812f6310756b8e30958002e8e8e41a7eb16f081058ff733b47c -R 4cc267c21faf6068f5badbc12783e171 +P 456691649aa2a7672d5d110acdde92426a9d34552863db3e0c86b73d9c5d9aac +R 4258bda3ec968b33da5f24ae8a1129aa U stephan -Z 92a4b7bac87478c8b69fd6008856678d +Z 1a99f4c0beb5aad623e0f3c7cf22f4ec # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index faff09de56..7d2ea99113 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -456691649aa2a7672d5d110acdde92426a9d34552863db3e0c86b73d9c5d9aac \ No newline at end of file +dc823bf00f78e7cd626329220c42c46da12d565e3273a08eda5fb512c1d807c6 \ No newline at end of file From 5f4e94722b2b301260578484d61116b2311cb598 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 13:05:12 +0000 Subject: [PATCH 085/148] Correct JNI sqlite3_prepare() to emit a null stmt handle when the C counterpart succeeds but results in a NULL pointer. FossilOrigin-Name: 94628f88b5cc82832f0ca2b00fd5346bfe99323097c6e659c5ac818c4e31d3e9 --- ext/jni/src/c/sqlite3-jni.c | 9 +++++++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 7d4c4f8580..50676c3519 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2717,13 +2717,18 @@ end: assert(zTail ? (((int)((void*)zTail - (void*)pBuf)) >= 0) : 1); OutputPointer_set_Int32(env, outTail, (int)(zTail ? (zTail - (const char *)pBuf) : 0)); } - NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + if( pStmt ){ + NativePointerHolder_set(env, jStmt, pStmt, S3JniClassNames.sqlite3_stmt); + }else{ + /* Happens for comments and whitespace */ + UNREF_L(jStmt); + jStmt = 0; + } }else{ UNREF_L(jStmt); jStmt = 0; } OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); - //NativePointerHolder_set(env, jOutStmt, pStmt, S3JniClassNames.sqlite3_stmt); (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } diff --git a/manifest b/manifest index 95c9daba02..7734265075 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Implement\sthe\snew/open/close\sSQLTester\scommands. -D 2023-08-08T11:46:46.234 +C Correct\sJNI\ssqlite3_prepare()\sto\semit\sa\snull\sstmt\shandle\swhen\sthe\sC\scounterpart\ssucceeds\sbut\sresults\sin\sa\sNULL\spointer. +D 2023-08-08T13:05:12.977 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 0d071597509ef4a9ac4b7712dac9ef29ded0db4819721c3b3c15e24d534827f6 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c eb105982266523f4cd9c5007c3cc713855aca520a0f24fce138e4ecfd573fc20 +F ext/jni/src/c/sqlite3-jni.c d51d930573dc4b13a02a66da9281b3ef814aeabd4c294bf3d7fc499093237224 F ext/jni/src/c/sqlite3-jni.h bc3ecd3f6e479fd45b80214f6256584cc599336ae222822fa1e603c22ff1fb19 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 456691649aa2a7672d5d110acdde92426a9d34552863db3e0c86b73d9c5d9aac -R 4258bda3ec968b33da5f24ae8a1129aa +P dc823bf00f78e7cd626329220c42c46da12d565e3273a08eda5fb512c1d807c6 +R 0e2bbb50341a703c4c5553e3f675aa5d U stephan -Z 1a99f4c0beb5aad623e0f3c7cf22f4ec +Z 290ed88efbcec0720b83e81f8130fc6f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7d2ea99113..bf60648a71 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dc823bf00f78e7cd626329220c42c46da12d565e3273a08eda5fb512c1d807c6 \ No newline at end of file +94628f88b5cc82832f0ca2b00fd5346bfe99323097c6e659c5ac818c4e31d3e9 \ No newline at end of file From aa291406371249f9a45b05392d94dcae54ba80fa Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 8 Aug 2023 14:25:47 +0000 Subject: [PATCH 086/148] Add a few words of clarification on how the SQLite Test Script Interpreter should be initialized to start each test script. FossilOrigin-Name: 3aa2b5a5cadb214dc64a3db412b7dfdd805abd8681b61da857b886cba3b937b5 --- .../jni/tester/test-script-interpreter.md | 20 +++++++++++++++++++ manifest | 14 ++++++------- manifest.uuid | 2 +- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 998bbeadcc..345210b1dd 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -35,6 +35,26 @@ script are deleted when the script finishes. The various commands will have access to this input buffer. Some commands will reset the buffer. +## Initialization + +The initial state of the interpreter at the start of processing each script +is as if the following command sequence had been run: + +> ~~~ +--close all +--db 0 +--new test.db +--null nil +~~~ + +In words, all database connections are closed except for connection 0 (the +default) which is open on an empty database named "test.db". The string +"nil" is displayed for NULL column values. + +The only context carried forward after the evaluation of one test script +into the evaluation of the next test script is the count of the number of +tests run and the number of failures seen. + ## Commands: Each command looks like an SQL comment. The command begins at the left diff --git a/manifest b/manifest index 7734265075..1616f77eaf 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\sJNI\ssqlite3_prepare()\sto\semit\sa\snull\sstmt\shandle\swhen\sthe\sC\scounterpart\ssucceeds\sbut\sresults\sin\sa\sNULL\spointer. -D 2023-08-08T13:05:12.977 +C Add\sa\sfew\swords\sof\sclarification\son\show\sthe\sSQLite\sTest\sScript\sInterpreter\nshould\sbe\sinitialized\sto\sstart\seach\stest\sscript. +D 2023-08-08T14:25:47.658 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e8597 F ext/jni/src/org/sqlite/jni/tester/Outer.java 07b5d68bdc4a01173f2515954a250dce6affcc4efb85b1ac50d24ad05b166bf4 F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2261be136c31a432a7416fa86810b97a28a2ad8012463ae68f0975ceac38b42f F ext/jni/src/org/sqlite/jni/tester/TestScript.java 38652e01cab9c07b20741829f54ef2f4a5c25a73b2c77213dd9198d4268acc51 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 99d2b8521af9362382ca57b19ae2f3f48db9909221aa9b20ae1a97255120760c +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md a37dd71d1bd24773699b6efaa7ee37e616ae6c2e6554e7d46e0f759dde71f3e3 F ext/jni/src/tests/000_first.test f58d5f22e2db31b20c6e744a247d14222508c88ed876b03a723c89f540948518 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P dc823bf00f78e7cd626329220c42c46da12d565e3273a08eda5fb512c1d807c6 -R 0e2bbb50341a703c4c5553e3f675aa5d -U stephan -Z 290ed88efbcec0720b83e81f8130fc6f +P 94628f88b5cc82832f0ca2b00fd5346bfe99323097c6e659c5ac818c4e31d3e9 +R 9b39d488601a6f92f9f4935ca5c0b938 +U drh +Z 2b5a8bdcd64c73cfaa2188a49297111d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bf60648a71..13b058113a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -94628f88b5cc82832f0ca2b00fd5346bfe99323097c6e659c5ac818c4e31d3e9 \ No newline at end of file +3aa2b5a5cadb214dc64a3db412b7dfdd805abd8681b61da857b886cba3b937b5 \ No newline at end of file From 671757f4d4daca431f1f67aca70e3b8ac8904a36 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 14:40:47 +0000 Subject: [PATCH 087/148] A number of baby steps for SQLTester.java. It can now handle basic --result cases. FossilOrigin-Name: 0404f688f6a22b6bbe009de1bee3341ca00e19e2cc32081265cf151876dc032f --- ext/jni/GNUmakefile | 1 + ext/jni/src/c/sqlite3-jni.c | 5 + ext/jni/src/org/sqlite/jni/ResultCode.java | 151 +++++++++++++ ext/jni/src/org/sqlite/jni/Tester1.java | 16 +- ext/jni/src/org/sqlite/jni/tester/Outer.java | 8 +- .../src/org/sqlite/jni/tester/SQLTester.java | 206 +++++++++++++++--- .../src/org/sqlite/jni/tester/TestScript.java | 19 +- .../jni/tester/test-script-interpreter.md | 10 +- ext/jni/src/tests/000_first.test | 21 +- manifest | 29 +-- manifest.uuid | 2 +- 11 files changed, 393 insertions(+), 75 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/ResultCode.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 66df526225..d6fc2e6d38 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -54,6 +54,7 @@ JAVA_FILES.main := $(patsubst %,$(dir.src.jni)/%,\ NativePointerHolder.java \ OutputPointer.java \ ProgressHandler.java \ + ResultCode.java \ RollbackHook.java \ SQLFunction.java \ sqlite3_context.java \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 50676c3519..66c62d6053 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2728,6 +2728,11 @@ end: UNREF_L(jStmt); jStmt = 0; } +#if 0 + if( 0!=rc ){ + MARKER(("prepare rc = %d\n", rc)); + } +#endif OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); (void)stmt_set_current(jc, pOldStmt); return (jint)rc; diff --git a/ext/jni/src/org/sqlite/jni/ResultCode.java b/ext/jni/src/org/sqlite/jni/ResultCode.java new file mode 100644 index 0000000000..30626f7564 --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/ResultCode.java @@ -0,0 +1,151 @@ +/* +** 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; + +//! Internal level of indirection requires because we cannot reference +// static enum members from an enum constructor. +class ResultCodeMap { + private static final java.util.Map i2e + = new java.util.HashMap<>(); + + public static void set(int i, ResultCode src){ + i2e.put(i, src); + } + public static ResultCode get(int i){ + return i2e.get(i); + } +} + +/** + This enum of sqlite3 result codes is provided not for use with the + C-style API (with which it won't work) but for higher-level code which + may find it useful to map codes to human-readable names. +*/ +public enum ResultCode { + SQLITE_OK(SQLite3Jni.SQLITE_OK), + SQLITE_ERROR(SQLite3Jni.SQLITE_ERROR), + SQLITE_INTERNAL(SQLite3Jni.SQLITE_INTERNAL), + SQLITE_PERM(SQLite3Jni.SQLITE_PERM), + SQLITE_ABORT(SQLite3Jni.SQLITE_ABORT), + SQLITE_BUSY(SQLite3Jni.SQLITE_BUSY), + SQLITE_LOCKED(SQLite3Jni.SQLITE_LOCKED), + SQLITE_NOMEM(SQLite3Jni.SQLITE_NOMEM), + SQLITE_READONLY(SQLite3Jni.SQLITE_READONLY), + SQLITE_INTERRUPT(SQLite3Jni.SQLITE_INTERRUPT), + SQLITE_IOERR(SQLite3Jni.SQLITE_IOERR), + SQLITE_CORRUPT(SQLite3Jni.SQLITE_CORRUPT), + SQLITE_NOTFOUND(SQLite3Jni.SQLITE_NOTFOUND), + SQLITE_FULL(SQLite3Jni.SQLITE_FULL), + SQLITE_CANTOPEN(SQLite3Jni.SQLITE_CANTOPEN), + SQLITE_PROTOCOL(SQLite3Jni.SQLITE_PROTOCOL), + SQLITE_EMPTY(SQLite3Jni.SQLITE_EMPTY), + SQLITE_SCHEMA(SQLite3Jni.SQLITE_SCHEMA), + SQLITE_TOOBIG(SQLite3Jni.SQLITE_TOOBIG), + SQLITE_CONSTRAINT(SQLite3Jni.SQLITE_CONSTRAINT), + SQLITE_MISMATCH(SQLite3Jni.SQLITE_MISMATCH), + SQLITE_MISUSE(SQLite3Jni.SQLITE_MISUSE), + SQLITE_NOLFS(SQLite3Jni.SQLITE_NOLFS), + SQLITE_AUTH(SQLite3Jni.SQLITE_AUTH), + SQLITE_FORMAT(SQLite3Jni.SQLITE_FORMAT), + SQLITE_RANGE(SQLite3Jni.SQLITE_RANGE), + SQLITE_NOTADB(SQLite3Jni.SQLITE_NOTADB), + SQLITE_NOTICE(SQLite3Jni.SQLITE_NOTICE), + SQLITE_WARNING(SQLite3Jni.SQLITE_WARNING), + SQLITE_ROW(SQLite3Jni.SQLITE_ROW), + SQLITE_DONE(SQLite3Jni.SQLITE_DONE), + SQLITE_ERROR_MISSING_COLLSEQ(SQLite3Jni.SQLITE_ERROR_MISSING_COLLSEQ), + SQLITE_ERROR_RETRY(SQLite3Jni.SQLITE_ERROR_RETRY), + SQLITE_ERROR_SNAPSHOT(SQLite3Jni.SQLITE_ERROR_SNAPSHOT), + SQLITE_IOERR_READ(SQLite3Jni.SQLITE_IOERR_READ), + SQLITE_IOERR_SHORT_READ(SQLite3Jni.SQLITE_IOERR_SHORT_READ), + SQLITE_IOERR_WRITE(SQLite3Jni.SQLITE_IOERR_WRITE), + SQLITE_IOERR_FSYNC(SQLite3Jni.SQLITE_IOERR_FSYNC), + SQLITE_IOERR_DIR_FSYNC(SQLite3Jni.SQLITE_IOERR_DIR_FSYNC), + SQLITE_IOERR_TRUNCATE(SQLite3Jni.SQLITE_IOERR_TRUNCATE), + SQLITE_IOERR_FSTAT(SQLite3Jni.SQLITE_IOERR_FSTAT), + SQLITE_IOERR_UNLOCK(SQLite3Jni.SQLITE_IOERR_UNLOCK), + SQLITE_IOERR_RDLOCK(SQLite3Jni.SQLITE_IOERR_RDLOCK), + SQLITE_IOERR_DELETE(SQLite3Jni.SQLITE_IOERR_DELETE), + SQLITE_IOERR_BLOCKED(SQLite3Jni.SQLITE_IOERR_BLOCKED), + SQLITE_IOERR_NOMEM(SQLite3Jni.SQLITE_IOERR_NOMEM), + SQLITE_IOERR_ACCESS(SQLite3Jni.SQLITE_IOERR_ACCESS), + SQLITE_IOERR_CHECKRESERVEDLOCK(SQLite3Jni.SQLITE_IOERR_CHECKRESERVEDLOCK), + SQLITE_IOERR_LOCK(SQLite3Jni.SQLITE_IOERR_LOCK), + SQLITE_IOERR_CLOSE(SQLite3Jni.SQLITE_IOERR_CLOSE), + SQLITE_IOERR_DIR_CLOSE(SQLite3Jni.SQLITE_IOERR_DIR_CLOSE), + SQLITE_IOERR_SHMOPEN(SQLite3Jni.SQLITE_IOERR_SHMOPEN), + SQLITE_IOERR_SHMSIZE(SQLite3Jni.SQLITE_IOERR_SHMSIZE), + SQLITE_IOERR_SHMLOCK(SQLite3Jni.SQLITE_IOERR_SHMLOCK), + SQLITE_IOERR_SHMMAP(SQLite3Jni.SQLITE_IOERR_SHMMAP), + SQLITE_IOERR_SEEK(SQLite3Jni.SQLITE_IOERR_SEEK), + SQLITE_IOERR_DELETE_NOENT(SQLite3Jni.SQLITE_IOERR_DELETE_NOENT), + SQLITE_IOERR_MMAP(SQLite3Jni.SQLITE_IOERR_MMAP), + SQLITE_IOERR_GETTEMPPATH(SQLite3Jni.SQLITE_IOERR_GETTEMPPATH), + SQLITE_IOERR_CONVPATH(SQLite3Jni.SQLITE_IOERR_CONVPATH), + SQLITE_IOERR_VNODE(SQLite3Jni.SQLITE_IOERR_VNODE), + SQLITE_IOERR_AUTH(SQLite3Jni.SQLITE_IOERR_AUTH), + SQLITE_IOERR_BEGIN_ATOMIC(SQLite3Jni.SQLITE_IOERR_BEGIN_ATOMIC), + SQLITE_IOERR_COMMIT_ATOMIC(SQLite3Jni.SQLITE_IOERR_COMMIT_ATOMIC), + SQLITE_IOERR_ROLLBACK_ATOMIC(SQLite3Jni.SQLITE_IOERR_ROLLBACK_ATOMIC), + SQLITE_IOERR_DATA(SQLite3Jni.SQLITE_IOERR_DATA), + SQLITE_IOERR_CORRUPTFS(SQLite3Jni.SQLITE_IOERR_CORRUPTFS), + SQLITE_LOCKED_SHAREDCACHE(SQLite3Jni.SQLITE_LOCKED_SHAREDCACHE), + SQLITE_LOCKED_VTAB(SQLite3Jni.SQLITE_LOCKED_VTAB), + SQLITE_BUSY_RECOVERY(SQLite3Jni.SQLITE_BUSY_RECOVERY), + SQLITE_BUSY_SNAPSHOT(SQLite3Jni.SQLITE_BUSY_SNAPSHOT), + SQLITE_BUSY_TIMEOUT(SQLite3Jni.SQLITE_BUSY_TIMEOUT), + SQLITE_CANTOPEN_NOTEMPDIR(SQLite3Jni.SQLITE_CANTOPEN_NOTEMPDIR), + SQLITE_CANTOPEN_ISDIR(SQLite3Jni.SQLITE_CANTOPEN_ISDIR), + SQLITE_CANTOPEN_FULLPATH(SQLite3Jni.SQLITE_CANTOPEN_FULLPATH), + SQLITE_CANTOPEN_CONVPATH(SQLite3Jni.SQLITE_CANTOPEN_CONVPATH), + SQLITE_CANTOPEN_SYMLINK(SQLite3Jni.SQLITE_CANTOPEN_SYMLINK), + SQLITE_CORRUPT_VTAB(SQLite3Jni.SQLITE_CORRUPT_VTAB), + SQLITE_CORRUPT_SEQUENCE(SQLite3Jni.SQLITE_CORRUPT_SEQUENCE), + SQLITE_CORRUPT_INDEX(SQLite3Jni.SQLITE_CORRUPT_INDEX), + SQLITE_READONLY_RECOVERY(SQLite3Jni.SQLITE_READONLY_RECOVERY), + SQLITE_READONLY_CANTLOCK(SQLite3Jni.SQLITE_READONLY_CANTLOCK), + SQLITE_READONLY_ROLLBACK(SQLite3Jni.SQLITE_READONLY_ROLLBACK), + SQLITE_READONLY_DBMOVED(SQLite3Jni.SQLITE_READONLY_DBMOVED), + SQLITE_READONLY_CANTINIT(SQLite3Jni.SQLITE_READONLY_CANTINIT), + SQLITE_READONLY_DIRECTORY(SQLite3Jni.SQLITE_READONLY_DIRECTORY), + SQLITE_ABORT_ROLLBACK(SQLite3Jni.SQLITE_ABORT_ROLLBACK), + SQLITE_CONSTRAINT_CHECK(SQLite3Jni.SQLITE_CONSTRAINT_CHECK), + SQLITE_CONSTRAINT_COMMITHOOK(SQLite3Jni.SQLITE_CONSTRAINT_COMMITHOOK), + SQLITE_CONSTRAINT_FOREIGNKEY(SQLite3Jni.SQLITE_CONSTRAINT_FOREIGNKEY), + SQLITE_CONSTRAINT_FUNCTION(SQLite3Jni.SQLITE_CONSTRAINT_FUNCTION), + SQLITE_CONSTRAINT_NOTNULL(SQLite3Jni.SQLITE_CONSTRAINT_NOTNULL), + SQLITE_CONSTRAINT_PRIMARYKEY(SQLite3Jni.SQLITE_CONSTRAINT_PRIMARYKEY), + SQLITE_CONSTRAINT_TRIGGER(SQLite3Jni.SQLITE_CONSTRAINT_TRIGGER), + SQLITE_CONSTRAINT_UNIQUE(SQLite3Jni.SQLITE_CONSTRAINT_UNIQUE), + SQLITE_CONSTRAINT_VTAB(SQLite3Jni.SQLITE_CONSTRAINT_VTAB), + SQLITE_CONSTRAINT_ROWID(SQLite3Jni.SQLITE_CONSTRAINT_ROWID), + SQLITE_CONSTRAINT_PINNED(SQLite3Jni.SQLITE_CONSTRAINT_PINNED), + SQLITE_CONSTRAINT_DATATYPE(SQLite3Jni.SQLITE_CONSTRAINT_DATATYPE), + SQLITE_NOTICE_RECOVER_WAL(SQLite3Jni.SQLITE_NOTICE_RECOVER_WAL), + SQLITE_NOTICE_RECOVER_ROLLBACK(SQLite3Jni.SQLITE_NOTICE_RECOVER_ROLLBACK), + SQLITE_WARNING_AUTOINDEX(SQLite3Jni.SQLITE_WARNING_AUTOINDEX), + SQLITE_AUTH_USER(SQLite3Jni.SQLITE_AUTH_USER), + SQLITE_OK_LOAD_PERMANENTLY(SQLite3Jni.SQLITE_OK_LOAD_PERMANENTLY); + + public final int value; + + ResultCode(int v){ + value = v; + ResultCodeMap.set(v, this); + } + + public static ResultCode getEntryForInt(int rc){ + return ResultCodeMap.get(rc); + } +} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 57df78e9f6..3c67076469 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -102,23 +102,27 @@ public class Tester1 { rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); if(throwOnError) affirm(0 == rc); else if( 0!=rc ) break; - stmt = outStmt.getValue(); pos = oTail.getValue(); + stmt = outStmt.getValue(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } affirm(0 != stmt.getNativePointer()); while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ } sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer()); if(0!=rc && SQLITE_ROW!=rc && SQLITE_DONE!=rc){ - if(throwOnError){ - throw new RuntimeException("db op failed with rc="+rc); - }else{ - break; - } + break; } } sqlite3_finalize(stmt); if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + if( 0!=rc && throwOnError){ + throw new RuntimeException("db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + } return rc; } diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java index f86277d14d..e4ea783490 100644 --- a/ext/jni/src/org/sqlite/jni/tester/Outer.java +++ b/ext/jni/src/org/sqlite/jni/tester/Outer.java @@ -29,8 +29,7 @@ class Outer { @SuppressWarnings("unchecked") public static void out(Object... vals){ - int n = 0; - for(Object v : vals) out((n++>0 ? " " : "")+v); + for(Object v : vals) out(v); } @SuppressWarnings("unchecked") @@ -41,7 +40,10 @@ class Outer { @SuppressWarnings("unchecked") public Outer verbose(Object... vals){ - if(verbose) outln(vals); + if(verbose){ + out("VERBOSE: "); + outln(vals); + } return this; } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 5fad8a6c0b..626e33a523 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -15,9 +15,16 @@ package org.sqlite.jni.tester; import java.util.List; import java.util.ArrayList; +import java.util.Arrays; +import java.nio.charset.StandardCharsets; import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; +class TestFailure extends RuntimeException { + public TestFailure(String msg){ + super(msg); + } +} /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -40,6 +47,7 @@ public class SQLTester { private int nTest; private final sqlite3[] aDb = new sqlite3[7]; private int iCurrentDb = 0; + private final String initialDbName = "test.db"; public SQLTester(){ reset(); @@ -48,6 +56,9 @@ public class SQLTester { public void setVerbose(boolean b){ this.outer.setVerbose(b); } + public boolean isVerbose(){ + return this.outer.isVerbose(); + } @SuppressWarnings("unchecked") public void verbose(Object... vals){ @@ -67,42 +78,51 @@ public class SQLTester { //! Adds the given test script to the to-test list. public void addTestScript(String filename){ listInFiles.add(filename); - verbose("Added file",filename); + verbose("Added file ",filename); + } + + public void setupInitialDb() throws Exception { + Util.unlink(initialDbName); + openDb(0, initialDbName, true); } public void runTests() throws Exception { // process each input file - outln("Verbose =",outer.isVerbose()); + outln("Verbose = ",outer.isVerbose()); for(String f : listInFiles){ reset(); + setupInitialDb(); ++nTestFile; final TestScript ts = new TestScript(f); - outln("---------> Test",ts.getName(),"..."); + outln("---------> Test ",ts.getName()," ..."); ts.run(this); - outln("<---------",nTest,"test(s) in",f); + outln("<--------- ",nTest," test(s) in ",f); } + Util.unlink(initialDbName); } - private StringBuilder resetBuffer(StringBuilder b){ + private StringBuilder clearBuffer(StringBuilder b){ b.delete(0, b.length()); return b; } - StringBuilder resetInputBuffer(){ - return resetBuffer(inputBuffer); + StringBuilder clearInputBuffer(){ + return clearBuffer(inputBuffer); } - StringBuilder resetResultBuffer(){ - return resetBuffer(resultBuffer); + StringBuilder clearResultBuffer(){ + return clearBuffer(resultBuffer); } StringBuilder getInputBuffer(){ return inputBuffer; } String getInputBufferText(){ return inputBuffer.toString(); } + String getResultBufferText(){ return resultBuffer.toString(); } + private String takeBuffer(StringBuilder b){ final String rc = b.toString(); - resetBuffer(b); + clearBuffer(b); return rc; } @@ -152,11 +172,18 @@ public class SQLTester { if( 0!=rc ){ final String msg = sqlite3_errmsg(db); sqlite3_close(db); - Util.toss("db open failed with code",rc,"and message:",msg); + Util.toss(TestFailure.class, "db open failed with code", + rc,"and message:",msg); } return aDb[iCurrentDb] = db; } + sqlite3 openDb(int slot, String name, boolean createIfNeeded) throws Exception { + affirmDbId(slot); + iCurrentDb = slot; + return openDb(name, createIfNeeded); + } + /** Resets all tester context state except for that related to tracking running totals. @@ -164,7 +191,7 @@ public class SQLTester { void reset(){ nTest = 0; nullView = "nil"; - resetInputBuffer(); + clearInputBuffer(); closeAllDbs(); } @@ -172,6 +199,89 @@ public class SQLTester { void incrementTestCounter(){ ++nTest; ++nTotalTest; } + String escapeSqlValue(String v){ + // TODO: implement the escaping rules + return v; + } + + private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ + sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)) + .append(' ') + .append(escapeSqlValue(sqlite3_errmsg(db))); + } + + public int execSql(sqlite3 db, boolean throwOnError, + boolean appendToResult, String sql) throws Exception { + final OutputPointer.Int32 oTail = new OutputPointer.Int32(); + final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); + final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); + if( null==db ) db = getCurrentDb(); + int pos = 0, n = 1; + byte[] sqlChunk = sqlUtf8; + int rc = 0; + sqlite3_stmt stmt = null; + final StringBuilder sb = appendToResult ? resultBuffer : null; + //outln("sqlChunk len= = ",sqlChunk.length); + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); + } + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + /*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ", + new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ + if( 0!=rc ){ + if(throwOnError){ + Util.toss(RuntimeException.class, "db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + }else if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + pos = oTail.getValue(); + stmt = outStmt.getValue(); + if( null == stmt ){ + // empty statement was parsed. + continue; + } + if( null!=sb ){ + // Add the output to the result buffer... + final int nCol = sqlite3_column_count(stmt); + int spacing = 0; + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + for(int i = 0; i < nCol; ++i){ + if( spacing++ > 0 ) sb.append(' '); + String val = sqlite3_column_text16(stmt, i); + if( null==val ){ + sb.append( nullView ); + continue; + } + sb.append( escapeSqlValue(val) ); + } + //sb.append('\n'); + } + }else{ + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} + } + sqlite3_finalize(stmt); + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + else if( rc!=0 ){ + if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; + } + } + sqlite3_finalize(stmt); + if( 0!=rc && throwOnError ){ + Util.toss(RuntimeException.class, "db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + } + return rc; + } + public static void main(String[] argv) throws Exception{ final SQLTester t = new SQLTester(); for(String a : argv){ @@ -189,7 +299,7 @@ public class SQLTester { t.addTestScript(a); } t.runTests(); - t.outer.outln("Processed",t.nTotalTest,"test(s) in",t.nTestFile,"file(s)."); + t.outer.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); } } @@ -221,6 +331,7 @@ class Command { protected final void argcCheck(String[] argv, int min, int max) throws Exception{ int argc = argv.length-1; + if(max<0) max = 99999999; if(argcmax){ if( min==max ) Util.badArg(argv[0],"requires exactly",min,"argument(s)"); else Util.badArg(argv[0],"requires",min,"-",max,"arguments."); @@ -254,7 +365,7 @@ class CloseDbCommand extends Command { if(argv.length>1){ String arg = argv[1]; if("all".equals(arg)){ - t.verbose(argv[0],"all dbs"); + t.verbose(argv[0]," all dbs"); t.closeAllDbs(); return; } @@ -265,7 +376,7 @@ class CloseDbCommand extends Command { id = t.getCurrentDbId(); } t.closeDb(id); - t.verbose(argv[0],"db",id); + t.verbose(argv[0]," db ",id); } } @@ -274,7 +385,7 @@ class DbCommand extends Command { argcCheck(argv,1); affirmNoContent(content); final sqlite3 db = t.setCurrentDb( Integer.parseInt(argv[1]) ); - t.verbose(argv[0],"set db to",db); + t.verbose(argv[0]," set db to ",db); } } @@ -284,7 +395,7 @@ class GlobCommand extends Command { argcCheck(argv,1); affirmNoContent(content); final String glob = argv[1].replace("#","[0-9]"); - t.verbose(argv[0],"is TODO. Pattern =",glob); + t.verbose(argv[0]," is TODO. Pattern = ",glob); } public GlobCommand(SQLTester t, String[] argv, String content) throws Exception{ this(false, t, argv, content); @@ -298,7 +409,7 @@ class NewDbCommand extends Command { String fname = argv[1]; Util.unlink(fname); final sqlite3 db = t.openDb(fname, true); - t.verbose(argv[0],"db",db); + t.verbose(argv[0]," db ",db); } } @@ -318,7 +429,7 @@ class NullCommand extends Command { argcCheck(argv,1); affirmNoContent(content); t.setNullValue(argv[1]); - //t.verbose(argv[0],argv[1]); + //t.verbose(argv[0]," ",argv[1]); } } @@ -327,9 +438,8 @@ class OpenDbCommand extends Command { argcCheck(argv,1); affirmNoContent(content); String fname = argv[1]; - Util.unlink(fname); final sqlite3 db = t.openDb(fname, false); - t.verbose(argv[0],"db",db); + t.verbose(argv[0]," db ",db); } } @@ -343,9 +453,37 @@ class PrintCommand extends Command { class ResultCommand extends Command { public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0); - //t.verbose(argv[0],"command is TODO"); + argcCheck(argv,1,-1); + affirmNoContent(content); t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, true, true, sql); + final String result = t.getResultBufferText().trim(); + StringBuilder sbExpect = new StringBuilder(); + for(int i = 1; i < argv.length; ++i ){ + if( i>1 ) sbExpect.append(" "); + sbExpect.append( argv[i] ); + } + final String sArgs = sbExpect.toString(); + //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + if( !result.equals(sArgs) ){ + Util.toss(TestFailure.class, argv[0]," comparison failed."); + } + } +} + +class RunCommand extends Command { + public RunCommand(SQLTester t, String[] argv, String content) throws Exception{ + argcCheck(argv,0); + affirmHasContent(content); + int rc = t.execSql(null, false, false, content); + if( 0!=rc ){ + sqlite3 db = t.getCurrentDb(); + String msg = sqlite3_errmsg(db); + t.verbose(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",content); + } } } @@ -353,9 +491,10 @@ class TestCaseCommand extends Command { public TestCaseCommand(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmHasContent(content); - t.resetInputBuffer(); - t.resetResultBuffer().append(content); - t.verbose(argv[0],"result buffer:",content); + // TODO: do something with the test name + t.clearResultBuffer(); + t.clearInputBuffer().append(content); + //t.verbose(argv[0]," input buffer: ",content); } } @@ -373,6 +512,7 @@ class CommandDispatcher { case "open": return OpenDbCommand.class; case "print": return PrintCommand.class; case "result": return ResultCommand.class; + case "run": return RunCommand.class; case "testcase": return TestCaseCommand.class; default: return null; } @@ -382,14 +522,17 @@ class CommandDispatcher { static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{ final Class cmdClass = getCommandByName(argv[0]); if(null == cmdClass){ - throw new IllegalArgumentException( - "No command handler found for '"+argv[0]+"'" - ); + Util.toss(IllegalArgumentException.class, + "No command handler found for '"+argv[0]+"'"); } final java.lang.reflect.Constructor ctor = cmdClass.getConstructor(SQLTester.class, String[].class, String.class); - //tester.verbose("Running",argv[0],"..."); - ctor.newInstance(tester, argv, content); + try{ + //tester.verbose("Running ",argv[0]," with:\n", content); + ctor.newInstance(tester, argv, content); + }catch(java.lang.reflect.InvocationTargetException e){ + throw (Exception)e.getCause(); + } } } @@ -419,5 +562,4 @@ final class Util { /* ignore */ } } - } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index c1e26e7a0a..d5af694faa 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -84,7 +84,7 @@ class TestScript { (because it contains certain content which indicates such). */ public static boolean shouldBeIgnored(String content){ - return content.indexOf("SCRIPT_MODULE_NAME")>=0 + return content.indexOf("SCRIPT_MODULE_NAME")<0 || content.indexOf("\n|")>=0; } @@ -123,16 +123,16 @@ class TestScript { s, Pattern.MULTILINE ); final Matcher m = p.matcher(tmp); - /*verbose("Pattern {{{",p.pattern(),"}}} with flags", - ""+p.flags(),"matches:" + /*verbose("Pattern {{{ ",p.pattern()," }}} with flags ", + p.flags()," matches:" );*/ int n = 0; - //while( m.find() ) verbose("#"+(++n)+"\t",m.group(0).trim()); + //while( m.find() ) verbose("#",(++n),"\t",m.group(0).trim()); tmp = m.replaceAll(""); } // Chunk the newly-cleaned text into individual commands and their input... final List rc = new ArrayList<>(); - final Pattern p = Pattern.compile("^--", Pattern.MULTILINE); + final Pattern p = Pattern.compile("^--[a-z]", Pattern.MULTILINE); final Matcher m = p.matcher(tmp); int ndxPrev = 0, pos = 0, i = 0; String chunk; @@ -147,7 +147,7 @@ class TestScript { } if( !chunk.isEmpty() ){ ++i; - //verbose("CHUNK #"+i,""+ndxPrev,"..",""+pos,chunk); + //verbose("CHUNK #",i," ",+ndxPrev,"..",pos,chunk); rc.add( chunk ); } ndxPrev = pos + 2; @@ -157,7 +157,7 @@ class TestScript { chunk = tmp.substring(ndxPrev, tmp.length()).trim(); if( !chunk.isEmpty() ){ ++i; - //verbose("CHUNK #"+(++i),chunk); + //verbose("CHUNK #",(++i)," ",chunk); rc.add( chunk ); } } @@ -168,13 +168,14 @@ class TestScript { Runs this test script in the context of the given tester object. */ public void run(SQLTester tester) throws Exception { + this.setVerbose(tester.isVerbose()); if( null==chunks ){ - verbose("This contains content which forces it to be ignored."); + outer.outln("This test contains content which forces it to be skipped."); }else{ int n = 0; for(String chunk : chunks){ ++n; - //verbose("#"+n,c).verbose(""); + //outer.verbose("CHUNK #",n," ",chunk,""); final String[] parts = chunk.split("\\n", 2); final String[] argv = parts[0].split("\\s+"); CommandDispatcher.dispatch( diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 345210b1dd..ffaec1b132 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -76,11 +76,11 @@ commands: ### The --testcase command -Every test case starts with a --testcase command. The --testcase command -resets both the "input buffer" and the "result buffer". -The argument to the --testcase command is the -name of the test case. That test case name is used for logging and debugging -and when printing errors. +Every test case starts with a --testcase command. The --testcase +command resets both the "input buffer" and the "result buffer". The +argument to the --testcase command is the name of the test case. That +test case name is used for logging and debugging and when printing +errors. The input buffer is set to the body of the test case. ### The --result command diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 9321576245..c838582145 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -1,4 +1,8 @@ -/* A script for testing the org.sqlite.jni.tester infrastructure */ +/* A script for testing the org.sqlite.jni.tester infrastructure +** +** SCRIPT_MODULE_NAME: 000_first +** +*/ # this line is ignored @@ -6,15 +10,19 @@ junk --new SQLTester.db --null zilch +--run +select 1; +select 2; +-- comment +intentional syntax error --oom --print This is from the print command. --- also ignored --testcase first -input for the first -command; ---result -hello world +select 'a', 'b'; +select 'a', 'b'; +--result a b a b --testcase second select 1 --glob # /* ignored */ @@ -22,3 +30,6 @@ select 1 select 'a' --notglob # --close +--open SQLTester.db +--print +Re-opened db. diff --git a/manifest b/manifest index 1616f77eaf..e879603840 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\sfew\swords\sof\sclarification\son\show\sthe\sSQLite\sTest\sScript\sInterpreter\nshould\sbe\sinitialized\sto\sstart\seach\stest\sscript. -D 2023-08-08T14:25:47.658 +C A\snumber\sof\sbaby\ssteps\sfor\sSQLTester.java.\sIt\scan\snow\shandle\sbasic\s--result\scases. +D 2023-08-08T14:40:47.725 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,9 +230,9 @@ 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 0d071597509ef4a9ac4b7712dac9ef29ded0db4819721c3b3c15e24d534827f6 +F ext/jni/GNUmakefile ce9587e2a4be50babc2ef4600e5c1016340a1187b9e5770c78000acdd02996bd F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c d51d930573dc4b13a02a66da9281b3ef814aeabd4c294bf3d7fc499093237224 +F ext/jni/src/c/sqlite3-jni.c ae704d2486e1213ecdc78bbf62d00573d41409d375cc7a3b8e4720f8a764d0cd F ext/jni/src/c/sqlite3-jni.h bc3ecd3f6e479fd45b80214f6256584cc599336ae222822fa1e603c22ff1fb19 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d @@ -249,10 +249,11 @@ F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b5 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee F ext/jni/src/org/sqlite/jni/OutputPointer.java ebdd33d48064c3302d0d4a6dd345562a967f8420edad7c7509403be277d076a0 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc +F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3681e6ea94973ce1f7facd6887853a4ae5657a9274dd06279b586dbf77f36c2d -F ext/jni/src/org/sqlite/jni/Tester1.java 57404879fbea78f0b405b7643abb03dad0a6ce6cea9ec0c4ef55ea40267be565 +F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -264,11 +265,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/Outer.java 07b5d68bdc4a01173f2515954a250dce6affcc4efb85b1ac50d24ad05b166bf4 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2261be136c31a432a7416fa86810b97a28a2ad8012463ae68f0975ceac38b42f -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 38652e01cab9c07b20741829f54ef2f4a5c25a73b2c77213dd9198d4268acc51 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md a37dd71d1bd24773699b6efaa7ee37e616ae6c2e6554e7d46e0f759dde71f3e3 -F ext/jni/src/tests/000_first.test f58d5f22e2db31b20c6e744a247d14222508c88ed876b03a723c89f540948518 +F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f16d95e0eb89723010de955197164ea5da58a83852499004ec510f778681a8da +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md e66ee0de4f6b805afe7486f6826d4e1f26b449066c2ec6550b314950369a4ea2 +F ext/jni/src/tests/000_first.test 752aca36279f9b0ceedaf15a4ce6bc9e0b7f9ca2749287e204d81ca2f7e41e6f F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 94628f88b5cc82832f0ca2b00fd5346bfe99323097c6e659c5ac818c4e31d3e9 -R 9b39d488601a6f92f9f4935ca5c0b938 -U drh -Z 2b5a8bdcd64c73cfaa2188a49297111d +P 3aa2b5a5cadb214dc64a3db412b7dfdd805abd8681b61da857b886cba3b937b5 +R 879a6e39ab778945869b54b1f498f7c6 +U stephan +Z d614dd3e6aa20cfdef1208a3dc72efbf # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 13b058113a..19673fabb6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3aa2b5a5cadb214dc64a3db412b7dfdd805abd8681b61da857b886cba3b937b5 \ No newline at end of file +0404f688f6a22b6bbe009de1bee3341ca00e19e2cc32081265cf151876dc032f \ No newline at end of file From 358c531e9b19d5f6f177b8ce3f3a6c010dcb540f Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 8 Aug 2023 14:49:16 +0000 Subject: [PATCH 088/148] Updates to the test script interpreter spec: Rather than failing immediately upon encountering an incompatibility, simply abandon the rest of that particular input file. FossilOrigin-Name: d2c99b96f4b61561c3fa34947ca7bfd2cd214b1913aff7ba64b7b897a574fea3 --- .../jni/tester/test-script-interpreter.md | 48 ++++++++++++++----- manifest | 14 +++--- manifest.uuid | 2 +- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index ffaec1b132..ec1ea53005 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -16,22 +16,42 @@ script are deleted when the script finishes. ## Parsing Rules: - 1. Ignore the entire script if the script does not contain the - string "SCRIPT_MODULE_NAME:". + 1. The test script is read line by line, where a line is a sequence of + characters that runs up to the next '\\n' (0x0a) character or until + the end of the file. There is never a need to read ahead past the + end of the current line. - 2. Ignore any script that contains the character sequence "\\n\|" - (0x0a, 0x7c). In other words, ignore scripts that contain the - pipe character at the beginning of a line. Such lines represent - test database file content in the "dbtotxt" format. We might add - support for this later, but omit it for the first version. + 2. If any line contains the string " MODULE_NAME:" (with a space before + the initial "M") or "MIXED_MODULE_NAME:" then that test script is + incompatible with this spec. Processing of the test script should + end immediately. There is no need to read any more of the file. + In verbose mode, the interpreter might choose to emit an informational + messages saying that the test script was abandoned due to an + incompatible module type. - 3. Ignore individual lines that begin with '#' (C-preprocessor lines). + 3. If any line contains the string "SCRIPT_MODULE_NAME:" then the input + script is known to be of the correct type for this specification and + processing may continue. The "MODULE_NAME" checking in steps 2 and 3 + may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME". - 4. If a line begins with exactly two minus signs followed by a + 4. If any line begins with the "\|" (0x7c) character, that indicates that + the input script is not compatible with this specification. Processing + of the script should stop immediately. In verbose mode, the interpreter + might choose to emit an informational message indicating that the + test script was abandoned because it contained "a dbtotxt format database + specification". + + 5. Any line that begins with "#" is a C-preprocessor line. The interpreter + described by this spec does not know how to deal with C-preprocessor lines. + Hence, processing should be abandoned. In verbose mode, the interpreter + might emit an informational message similar to + "script NAME abandoned due to C-preprocessor line: ..." + + 6. If a line begins with exactly two minus signs followed by a lowercase letter, that is a command. Process commands as described below. - 5. All other lines should be accumulated into the "input buffer". + 7. All other lines should be accumulated into the "input buffer". The various commands will have access to this input buffer. Some commands will reset the buffer. @@ -67,8 +87,12 @@ Commands have access to the input buffer and might reset the input buffer. The command can also optionally read (and consume) additional text from script that comes after the command. -Unknown or unrecognized commands should cause an error message to be -printed and execution to stop. +Unknown or unrecognized commands indicate that the script contains features +that are not (yet) supported by this specification. Processing of the +script should terminate immediately. When this happens and when the +interpreter is in a "verbose" mode, the interpreter might choose to emit +an informational message along the lines of "test script NAME abandoned +due to unsupported command: --whatever". The initial implemention will only recognize a few commands. Other commands may be added later. The following is the initial set of diff --git a/manifest b/manifest index e879603840..4656865a93 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C A\snumber\sof\sbaby\ssteps\sfor\sSQLTester.java.\sIt\scan\snow\shandle\sbasic\s--result\scases. -D 2023-08-08T14:40:47.725 +C Updates\sto\sthe\stest\sscript\sinterpreter\sspec:\s\sRather\sthan\sfailing\simmediately\nupon\sencountering\san\sincompatibility,\ssimply\sabandon\sthe\srest\sof\sthat\sparticular\ninput\sfile. +D 2023-08-08T14:49:16.921 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -268,7 +268,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e8597 F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f16d95e0eb89723010de955197164ea5da58a83852499004ec510f778681a8da F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md e66ee0de4f6b805afe7486f6826d4e1f26b449066c2ec6550b314950369a4ea2 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c F ext/jni/src/tests/000_first.test 752aca36279f9b0ceedaf15a4ce6bc9e0b7f9ca2749287e204d81ca2f7e41e6f F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 3aa2b5a5cadb214dc64a3db412b7dfdd805abd8681b61da857b886cba3b937b5 -R 879a6e39ab778945869b54b1f498f7c6 -U stephan -Z d614dd3e6aa20cfdef1208a3dc72efbf +P 0404f688f6a22b6bbe009de1bee3341ca00e19e2cc32081265cf151876dc032f +R 5c771fdf09ed554b70864c3b42074aa2 +U drh +Z a318dc74afcddc68936c41fffb67c044 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 19673fabb6..940c759510 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0404f688f6a22b6bbe009de1bee3341ca00e19e2cc32081265cf151876dc032f \ No newline at end of file +d2c99b96f4b61561c3fa34947ca7bfd2cd214b1913aff7ba64b7b897a574fea3 \ No newline at end of file From bff14ecfcc61f5e0131d182c44b1f228bc21dede Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 14:58:00 +0000 Subject: [PATCH 089/148] Correct the spacing output of multi-select SQL blocks for SQLTester --result. FossilOrigin-Name: 8d98645a9e524b30f7faa1cffd8f09e7aab3c25ac7b08dd6884141dfe9cdb0d3 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 7 ++++--- manifest | 14 +++++++------- manifest.uuid | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 626e33a523..9273faae47 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -206,8 +206,9 @@ public class SQLTester { private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)) - .append(' ') - .append(escapeSqlValue(sqlite3_errmsg(db))); + .append(" {") + .append(escapeSqlValue(sqlite3_errmsg(db))) + .append("}"); } public int execSql(sqlite3 db, boolean throwOnError, @@ -220,6 +221,7 @@ public class SQLTester { byte[] sqlChunk = sqlUtf8; int rc = 0; sqlite3_stmt stmt = null; + int spacing = 0 /* emit a space for --result if>0 */ ; final StringBuilder sb = appendToResult ? resultBuffer : null; //outln("sqlChunk len= = ",sqlChunk.length); while(pos < sqlChunk.length){ @@ -249,7 +251,6 @@ public class SQLTester { if( null!=sb ){ // Add the output to the result buffer... final int nCol = sqlite3_column_count(stmt); - int spacing = 0; while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ for(int i = 0; i < nCol; ++i){ if( spacing++ > 0 ) sb.append(' '); diff --git a/manifest b/manifest index 4656865a93..1986118349 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Updates\sto\sthe\stest\sscript\sinterpreter\sspec:\s\sRather\sthan\sfailing\simmediately\nupon\sencountering\san\sincompatibility,\ssimply\sabandon\sthe\srest\sof\sthat\sparticular\ninput\sfile. -D 2023-08-08T14:49:16.921 +C Correct\sthe\sspacing\soutput\sof\smulti-select\sSQL\sblocks\sfor\sSQLTester\s--result. +D 2023-08-08T14:58:00.844 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f16d95e0eb89723010de955197164ea5da58a83852499004ec510f778681a8da +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java c26e0be30b03b9bf233903b0dd11314a38b8c47c7f9a322d9c383deab0f1799c F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c F ext/jni/src/tests/000_first.test 752aca36279f9b0ceedaf15a4ce6bc9e0b7f9ca2749287e204d81ca2f7e41e6f @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0404f688f6a22b6bbe009de1bee3341ca00e19e2cc32081265cf151876dc032f -R 5c771fdf09ed554b70864c3b42074aa2 -U drh -Z a318dc74afcddc68936c41fffb67c044 +P d2c99b96f4b61561c3fa34947ca7bfd2cd214b1913aff7ba64b7b897a574fea3 +R 93a03db65e40af7c6fe9793f15a6ca43 +U stephan +Z dca17c3e57d683e1e595814410eca997 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 940c759510..54be3ff1d2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d2c99b96f4b61561c3fa34947ca7bfd2cd214b1913aff7ba64b7b897a574fea3 \ No newline at end of file +8d98645a9e524b30f7faa1cffd8f09e7aab3c25ac7b08dd6884141dfe9cdb0d3 \ No newline at end of file From 746bb422ea82ec09e5404bbcfd40f1c64c4ada71 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 19:20:12 +0000 Subject: [PATCH 090/148] When SQLTester hits an unknown command, emit a warning and skip the rest of that script instead of aborting the whole run, per /chat discussion. Reduce verbosity a bit. FossilOrigin-Name: 3e78d22d04e6ac2606bfc5ce250a4c3b39a2062e14011ca0a8a0a85491efbfde --- .../src/org/sqlite/jni/tester/SQLTester.java | 80 +++++++++++++++---- ext/jni/src/tests/000_first.test | 3 +- manifest | 14 ++-- manifest.uuid | 2 +- 4 files changed, 73 insertions(+), 26 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 9273faae47..59fb499be7 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -25,6 +25,15 @@ class TestFailure extends RuntimeException { super(msg); } } + +class SkipTestRemainder extends RuntimeException { + public TestScript testScript; + public SkipTestRemainder(TestScript ts){ + super("Skipping remainder of "+ts.getName()); + testScript = ts; + } +} + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -44,10 +53,12 @@ public class SQLTester { private String nullView; private int nTotalTest = 0; private int nTestFile = 0; + private int nAbortedScript = 0; private int nTest; private final sqlite3[] aDb = new sqlite3[7]; private int iCurrentDb = 0; private final String initialDbName = "test.db"; + private TestScript currentScript; public SQLTester(){ reset(); @@ -86,17 +97,30 @@ public class SQLTester { openDb(0, initialDbName, true); } + TestScript getCurrentScript(){ + return currentScript; + } + public void runTests() throws Exception { // process each input file - outln("Verbose = ",outer.isVerbose()); - for(String f : listInFiles){ - reset(); - setupInitialDb(); - ++nTestFile; - final TestScript ts = new TestScript(f); - outln("---------> Test ",ts.getName()," ..."); - ts.run(this); - outln("<--------- ",nTest," test(s) in ",f); + try { + for(String f : listInFiles){ + reset(); + setupInitialDb(); + ++nTestFile; + final TestScript ts = new TestScript(f); + currentScript = ts; + outln("---------> Test ",ts.getName()," ..."); + try{ + ts.run(this); + }catch(SkipTestRemainder e){ + /* not an error */ + ++nAbortedScript; + } + outln("<--------- ",nTest," test(s) in ",f); + } + }finally{ + currentScript = null; } Util.unlink(initialDbName); } @@ -197,6 +221,16 @@ public class SQLTester { void setNullValue(String v){nullView = v;} + /** + If true, encountering an unknown command in a script causes the + remainder of the script to be skipped, rather than aborting the + whole script run. + */ + boolean skipUnknownCommands(){ + // Currently hard-coded. Potentially a flag someday. + return true; + } + void incrementTestCounter(){ ++nTest; ++nTotalTest; } String escapeSqlValue(String v){ @@ -290,6 +324,7 @@ public class SQLTester { final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ t.setVerbose(true); + t.outln("Verbose mode is on."); }else if( flag.equals("quiet") ) { t.setVerbose(false); }else{ @@ -300,7 +335,10 @@ public class SQLTester { t.addTestScript(a); } t.runTests(); - t.outer.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); + t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); + if( t.nAbortedScript > 0 ){ + t.outln("Aborted ",t.nAbortedScript," script(s)."); + } } } @@ -366,7 +404,7 @@ class CloseDbCommand extends Command { if(argv.length>1){ String arg = argv[1]; if("all".equals(arg)){ - t.verbose(argv[0]," all dbs"); + //t.verbose(argv[0]," all dbs"); t.closeAllDbs(); return; } @@ -377,7 +415,7 @@ class CloseDbCommand extends Command { id = t.getCurrentDbId(); } t.closeDb(id); - t.verbose(argv[0]," db ",id); + //t.verbose(argv[0]," db ",id); } } @@ -386,7 +424,7 @@ class DbCommand extends Command { argcCheck(argv,1); affirmNoContent(content); final sqlite3 db = t.setCurrentDb( Integer.parseInt(argv[1]) ); - t.verbose(argv[0]," set db to ",db); + //t.verbose(argv[0]," set db to ",db); } } @@ -410,7 +448,7 @@ class NewDbCommand extends Command { String fname = argv[1]; Util.unlink(fname); final sqlite3 db = t.openDb(fname, true); - t.verbose(argv[0]," db ",db); + //t.verbose(argv[0]," db ",db); } } @@ -440,7 +478,7 @@ class OpenDbCommand extends Command { affirmNoContent(content); String fname = argv[1]; final sqlite3 db = t.openDb(fname, false); - t.verbose(argv[0]," db ",db); + //t.verbose(argv[0]," db ",db); } } @@ -523,8 +561,15 @@ class CommandDispatcher { static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{ final Class cmdClass = getCommandByName(argv[0]); if(null == cmdClass){ + final TestScript ts = tester.getCurrentScript(); + if( tester.skipUnknownCommands() ){ + tester.outln("WARNING: skipping remainder of ",ts.getName(), + " because it contains unknown command '",argv[0],"'."); + throw new SkipTestRemainder(ts); + } Util.toss(IllegalArgumentException.class, - "No command handler found for '"+argv[0]+"'"); + "No command handler found for '"+argv[0]+"' in ", + ts.getName()); } final java.lang.reflect.Constructor ctor = cmdClass.getConstructor(SQLTester.class, String[].class, String.class); @@ -532,7 +577,8 @@ class CommandDispatcher { //tester.verbose("Running ",argv[0]," with:\n", content); ctor.newInstance(tester, argv, content); }catch(java.lang.reflect.InvocationTargetException e){ - throw (Exception)e.getCause(); + Throwable t = e.getCause(); + throw (t!=null && t instanceof Exception) ? (Exception)t : e; } } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index c838582145..031964d667 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -14,7 +14,7 @@ junk select 1; select 2; -- comment -intentional syntax error +-- uncomment to introduce intentional syntax error --oom --print This is from the print command. @@ -33,3 +33,4 @@ select 'a' --open SQLTester.db --print Re-opened db. +--an-uknown-command diff --git a/manifest b/manifest index 1986118349..b7af88ce9c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\sthe\sspacing\soutput\sof\smulti-select\sSQL\sblocks\sfor\sSQLTester\s--result. -D 2023-08-08T14:58:00.844 +C When\sSQLTester\shits\san\sunknown\scommand,\semit\sa\swarning\sand\sskip\sthe\srest\sof\sthat\sscript\sinstead\sof\saborting\sthe\swhole\srun,\sper\s/chat\sdiscussion.\sReduce\sverbosity\sa\sbit. +D 2023-08-08T19:20:12.267 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java c26e0be30b03b9bf233903b0dd11314a38b8c47c7f9a322d9c383deab0f1799c +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 099c256f46dd0fe9ea00e25e2adb2b56cf1ea5765214b1a07b234179f154e6d5 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c -F ext/jni/src/tests/000_first.test 752aca36279f9b0ceedaf15a4ce6bc9e0b7f9ca2749287e204d81ca2f7e41e6f +F ext/jni/src/tests/000_first.test 67c095b9ba8d1e57ea5f996126f8a9a76c2fffbe6c0b9d4083e604f7991e54d8 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d2c99b96f4b61561c3fa34947ca7bfd2cd214b1913aff7ba64b7b897a574fea3 -R 93a03db65e40af7c6fe9793f15a6ca43 +P 8d98645a9e524b30f7faa1cffd8f09e7aab3c25ac7b08dd6884141dfe9cdb0d3 +R a618fd6ff82f81612f61c1de1f6b473b U stephan -Z dca17c3e57d683e1e595814410eca997 +Z c4565e5d104af1325b5207d0f09e8da7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 54be3ff1d2..b087dc17eb 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8d98645a9e524b30f7faa1cffd8f09e7aab3c25ac7b08dd6884141dfe9cdb0d3 \ No newline at end of file +3e78d22d04e6ac2606bfc5ce250a4c3b39a2062e14011ca0a8a0a85491efbfde \ No newline at end of file From af825cd50b734367e775879f3648988bfca8927d Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 20:02:10 +0000 Subject: [PATCH 091/148] Add glob/notglob commands to SQLTester and complete the interrupted-midway impls of the strglob() and strlike() JNI bindings. FossilOrigin-Name: 4ba98ec0bf24c31cce498031cb3727e09f928f54ec13c76fec50e439e0f2ba15 --- ext/jni/src/c/sqlite3-jni.c | 17 ++++++--- ext/jni/src/c/sqlite3-jni.h | 4 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 21 +++++++---- .../src/org/sqlite/jni/tester/SQLTester.java | 37 +++++++++++++------ ext/jni/src/tests/000_first.test | 4 +- manifest | 20 +++++----- manifest.uuid | 2 +- 7 files changed, 66 insertions(+), 39 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 66c62d6053..84b382d510 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -3070,24 +3070,29 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ } static int s3jni_strlike_glob(int isLike, JNIEnv *const env, - jbyteArray baG, jbyteArray baT){ + jbyteArray baG, jbyteArray baT, jint escLike){ int rc = 0; jbyte * const pG = JBA_TOC(baG); jbyte * const pT = pG ? JBA_TOC(baT) : 0; - OOM_CHECK(pT); - rc = sqlite3_strglob((const char *)pG, (const char *)pT); + + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = isLike + ? sqlite3_strlike((const char *)pG, (const char *)pT, + (unsigned int)escLike) + : sqlite3_strglob((const char *)pG, (const char *)pT); JBA_RELEASE(baG, pG); JBA_RELEASE(baT, pT); return rc; } JDECL(int,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ - return s3jni_strlike_glob(0, env, baG, baT); + return s3jni_strlike_glob(0, env, baG, baT, 0); } -JDECL(int,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ - return s3jni_strlike_glob(1, env, baG, baT); +JDECL(int,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ + return s3jni_strlike_glob(1, env, baG, baT, escChar); } JDECL(jint,1shutdown)(JENV_CSELF){ diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index ea9b47e68f..b75bd6c6f8 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1470,10 +1470,10 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strglob /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_strlike - * Signature: ([B[B)I + * Signature: ([B[BI)I */ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1strlike - (JNIEnv *, jclass, jbyteArray, jbyteArray); + (JNIEnv *, jclass, jbyteArray, jbyteArray, jint); /* * Class: org_sqlite_jni_SQLite3Jni diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 63fb08ccca..257864e741 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -850,21 +850,28 @@ public final class SQLite3Jni { public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); - private static native int sqlite3_strglob(@NotNull byte[] glob, @NotNull byte[] txt); + private static native int sqlite3_strglob( + @NotNull byte[] glob, @NotNull byte[] txt + ); public static int sqlite3_strglob(@NotNull String glob, @NotNull String txt){ return sqlite3_strglob( - glob.getBytes(StandardCharsets.UTF_8), - txt.getBytes(StandardCharsets.UTF_8) + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8) ); } - private static native int sqlite3_strlike(@NotNull byte[] glob, @NotNull byte[] txt); + private static native int sqlite3_strlike( + @NotNull byte[] glob, @NotNull byte[] txt, int escChar + ); - public static int sqlite3_strlike(@NotNull String glob, @NotNull String txt){ + public static int sqlite3_strlike( + @NotNull String glob, @NotNull String txt, char escChar + ){ return sqlite3_strlike( - glob.getBytes(StandardCharsets.UTF_8), - txt.getBytes(StandardCharsets.UTF_8) + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8), + (int)escChar ); } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 59fb499be7..7c5d51f034 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -196,8 +196,8 @@ public class SQLTester { if( 0!=rc ){ final String msg = sqlite3_errmsg(db); sqlite3_close(db); - Util.toss(TestFailure.class, "db open failed with code", - rc,"and message:",msg); + Util.toss(TestFailure.class, "db open failed with code ", + rc," and message: ",msg); } return aDb[iCurrentDb] = db; } @@ -433,8 +433,20 @@ class GlobCommand extends Command { String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); + + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, true, true, sql); + final String result = t.getResultBufferText().trim(); + final String sArgs = Util.argvToString(argv); + //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); final String glob = argv[1].replace("#","[0-9]"); - t.verbose(argv[0]," is TODO. Pattern = ",glob); + rc = sqlite3_strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + Util.toss(TestFailure.class, this.getClass().getSimpleName(), + " glob mismatch: ",glob," vs input: ",result); + } } public GlobCommand(SQLTester t, String[] argv, String content) throws Exception{ this(false, t, argv, content); @@ -499,12 +511,7 @@ class ResultCommand extends Command { //t.verbose(argv[0]," SQL =\n",sql); int rc = t.execSql(null, true, true, sql); final String result = t.getResultBufferText().trim(); - StringBuilder sbExpect = new StringBuilder(); - for(int i = 1; i < argv.length; ++i ){ - if( i>1 ) sbExpect.append(" "); - sbExpect.append( argv[i] ); - } - final String sArgs = sbExpect.toString(); + final String sArgs = Util.argvToString(argv); //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); if( !result.equals(sArgs) ){ Util.toss(TestFailure.class, argv[0]," comparison failed."); @@ -586,8 +593,7 @@ class CommandDispatcher { final class Util { public static void toss(Class errorType, Object... msg) throws Exception { StringBuilder sb = new StringBuilder(); - int i = 0; - for(Object s : msg) sb.append(((0==i++) ? "" : " ")+s); + for(Object s : msg) sb.append(s); final java.lang.reflect.Constructor ctor = errorType.getConstructor(String.class); throw ctor.newInstance(sb.toString()); @@ -609,4 +615,13 @@ final class Util { /* ignore */ } } + + public static String argvToString(String[] argv){ + StringBuilder sb = new StringBuilder(); + for(int i = 1; i < argv.length; ++i ){ + if( i>1 ) sb.append(" "); + sb.append( argv[i] ); + } + return sb.toString(); + } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 031964d667..859a634ca0 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -24,8 +24,8 @@ select 'a', 'b'; select 'a', 'b'; --result a b a b --testcase second -select 1 ---glob # /* ignored */ +select 123 +--glob #2# --testcase second select 'a' --notglob # diff --git a/manifest b/manifest index b7af88ce9c..bb4ced2c63 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C When\sSQLTester\shits\san\sunknown\scommand,\semit\sa\swarning\sand\sskip\sthe\srest\sof\sthat\sscript\sinstead\sof\saborting\sthe\swhole\srun,\sper\s/chat\sdiscussion.\sReduce\sverbosity\sa\sbit. -D 2023-08-08T19:20:12.267 +C Add\sglob/notglob\scommands\sto\sSQLTester\sand\scomplete\sthe\sinterrupted-midway\simpls\sof\sthe\sstrglob()\sand\sstrlike()\sJNI\sbindings. +D 2023-08-08T20:02:10.677 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile ce9587e2a4be50babc2ef4600e5c1016340a1187b9e5770c78000acdd02996bd F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c ae704d2486e1213ecdc78bbf62d00573d41409d375cc7a3b8e4720f8a764d0cd -F ext/jni/src/c/sqlite3-jni.h bc3ecd3f6e479fd45b80214f6256584cc599336ae222822fa1e603c22ff1fb19 +F ext/jni/src/c/sqlite3-jni.c 2de1acf3f1300c52faf4d3cf5b54ece9550ec9e3952bafc7f6fd116102554b28 +F ext/jni/src/c/sqlite3-jni.h 2d91d092a298ded15300489303d5dea519333af61980afa64c58687aea40f03d F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -252,7 +252,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 3681e6ea94973ce1f7facd6887853a4ae5657a9274dd06279b586dbf77f36c2d +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 6d868925dd0260805e922b1a598d3e377f87f90e16cae327aa7b7beeecac45a9 F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 099c256f46dd0fe9ea00e25e2adb2b56cf1ea5765214b1a07b234179f154e6d5 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f6a6085a7edccbd455c460aded6fb51292c2a984b44c358d9dc9748344837f17 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c -F ext/jni/src/tests/000_first.test 67c095b9ba8d1e57ea5f996126f8a9a76c2fffbe6c0b9d4083e604f7991e54d8 +F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 8d98645a9e524b30f7faa1cffd8f09e7aab3c25ac7b08dd6884141dfe9cdb0d3 -R a618fd6ff82f81612f61c1de1f6b473b +P 3e78d22d04e6ac2606bfc5ce250a4c3b39a2062e14011ca0a8a0a85491efbfde +R 932601b64460ca8a5b7bc3723f2f11df U stephan -Z c4565e5d104af1325b5207d0f09e8da7 +Z 44e5531a596f12e4460d81bd8efc488d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b087dc17eb..68705388e5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3e78d22d04e6ac2606bfc5ce250a4c3b39a2062e14011ca0a8a0a85491efbfde \ No newline at end of file +4ba98ec0bf24c31cce498031cb3727e09f928f54ec13c76fec50e439e0f2ba15 \ No newline at end of file From c15dce9f9204e65e84d0a40265da7a0051bd51e2 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 20:15:42 +0000 Subject: [PATCH 092/148] If SQLTester now treats a no-args --result as comparing against an empty string. FossilOrigin-Name: 49005ca5cc191c52279bc7fdb45d95eeb6f8e344f78ce9dbd97aac814bc21202 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 4 ++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 7c5d51f034..337925d608 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -504,14 +504,14 @@ class PrintCommand extends Command { class ResultCommand extends Command { public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1,-1); + argcCheck(argv,0,1); affirmNoContent(content); t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); int rc = t.execSql(null, true, true, sql); final String result = t.getResultBufferText().trim(); - final String sArgs = Util.argvToString(argv); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); if( !result.equals(sArgs) ){ Util.toss(TestFailure.class, argv[0]," comparison failed."); diff --git a/manifest b/manifest index bb4ced2c63..efb4481d5e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sglob/notglob\scommands\sto\sSQLTester\sand\scomplete\sthe\sinterrupted-midway\simpls\sof\sthe\sstrglob()\sand\sstrlike()\sJNI\sbindings. -D 2023-08-08T20:02:10.677 +C If\sSQLTester\snow\streats\sa\sno-args\s--result\sas\scomparing\sagainst\san\sempty\sstring. +D 2023-08-08T20:15:42.693 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f6a6085a7edccbd455c460aded6fb51292c2a984b44c358d9dc9748344837f17 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java b7140bd14563ab831a51feeb17887b3a67175ee19a516f5e040fdbbf6fe6bb2b F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 3e78d22d04e6ac2606bfc5ce250a4c3b39a2062e14011ca0a8a0a85491efbfde -R 932601b64460ca8a5b7bc3723f2f11df +P 4ba98ec0bf24c31cce498031cb3727e09f928f54ec13c76fec50e439e0f2ba15 +R e261021efec2bf27dec0c1de8bf26411 U stephan -Z 44e5531a596f12e4460d81bd8efc488d +Z 2d312267f73405c5ee2bc23c77fa6453 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 68705388e5..99a825bab5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4ba98ec0bf24c31cce498031cb3727e09f928f54ec13c76fec50e439e0f2ba15 \ No newline at end of file +49005ca5cc191c52279bc7fdb45d95eeb6f8e344f78ce9dbd97aac814bc21202 \ No newline at end of file From 4d3aa08f673a1477f0735967340fdaaaf9a2159a Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 20:41:29 +0000 Subject: [PATCH 093/148] Correct --result arg count check and add infrastructure to let us add custom C-side behavior to SQLTester via an auto extension. FossilOrigin-Name: bb8321702eea52fa9d42987a4b053b32d8eba15580a39d7831cd8d6f1ceb62bf --- ext/jni/GNUmakefile | 4 +++- ext/jni/src/c/sqlite3-jni.c | 11 ++++++++++ ext/jni/src/c/sqlite3-jni.h | 21 +++++++++++++++++++ .../src/org/sqlite/jni/tester/SQLTester.java | 16 ++++++++++++-- manifest | 18 ++++++++-------- manifest.uuid | 2 +- 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index d6fc2e6d38..68687d0e32 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -175,7 +175,7 @@ sqlite3-jni.dll := $(dir.bld.c)/libsqlite3-jni.so sqlite3-jni.h.in := define ADD_JNI_H sqlite3-jni.h.in += $$(dir.bld.c)/org_sqlite_jni_$(1).h -$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).class +$$(dir.bld.c)/org_sqlite_jni_$(1).h: $$(dir.src.jni)/$(1).java endef $(eval $(call ADD_JNI_H,SQLite3Jni)) ifeq (1,$(enable.fts5)) @@ -183,6 +183,8 @@ ifeq (1,$(enable.fts5)) $(eval $(call ADD_JNI_H,fts5_api)) $(eval $(call ADD_JNI_H,fts5_tokenizer)) endif +sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h +$(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java #sqlite3-jni.dll.cfiles := $(dir.src.c) sqlite3-jni.dll.cflags := \ -fPIC \ diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 84b382d510..a46429bc58 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -4030,6 +4030,17 @@ Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; } +static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + //MARKER(("TODO: DUP() UDF\n")); + return 0; +} + + +JNIEXPORT void JNICALL +Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ + sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); +} /** Called during static init of the SQLite3Jni class to sync certain diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index b75bd6c6f8..e2cce883b3 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1918,3 +1918,24 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize } #endif #endif +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_sqlite_jni_tester_SQLTester */ + +#ifndef _Included_org_sqlite_jni_tester_SQLTester +#define _Included_org_sqlite_jni_tester_SQLTester +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_sqlite_jni_tester_SQLTester + * Method: installCustomExtensions + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions + (JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 337925d608..1629bde603 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -340,6 +340,18 @@ public class SQLTester { t.outln("Aborted ",t.nAbortedScript," script(s)."); } } + + + private static native void installCustomExtensions(); + static { + System.loadLibrary("sqlite3-jni") + /* Interestingly, when SQLTester is the main app, we have to + load that lib from here. The same load from SQLite3Jni does + not happen early enough. Without this, + installCustomExtensions() is an unresolved symbol. */; + installCustomExtensions(); + } + } /** @@ -373,7 +385,7 @@ class Command { if(max<0) max = 99999999; if(argcmax){ if( min==max ) Util.badArg(argv[0],"requires exactly",min,"argument(s)"); - else Util.badArg(argv[0],"requires",min,"-",max,"arguments."); + else Util.badArg(argv[0]," requires ",min,"-",max," arguments."); } } @@ -504,7 +516,7 @@ class PrintCommand extends Command { class ResultCommand extends Command { public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0,1); + argcCheck(argv,0,-1); affirmNoContent(content); t.incrementTestCounter(); final String sql = t.takeInputBuffer(); diff --git a/manifest b/manifest index efb4481d5e..2b3dc8f488 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C If\sSQLTester\snow\streats\sa\sno-args\s--result\sas\scomparing\sagainst\san\sempty\sstring. -D 2023-08-08T20:15:42.693 +C Correct\s--result\sarg\scount\scheck\sand\sadd\sinfrastructure\sto\slet\sus\sadd\scustom\sC-side\sbehavior\sto\sSQLTester\svia\san\sauto\sextension. +D 2023-08-08T20:41:29.013 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,10 +230,10 @@ 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 ce9587e2a4be50babc2ef4600e5c1016340a1187b9e5770c78000acdd02996bd +F ext/jni/GNUmakefile dcd9595bf9b218c8c97a60f558eaacedbd3901f63ed59475f38c73a7692dd084 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 2de1acf3f1300c52faf4d3cf5b54ece9550ec9e3952bafc7f6fd116102554b28 -F ext/jni/src/c/sqlite3-jni.h 2d91d092a298ded15300489303d5dea519333af61980afa64c58687aea40f03d +F ext/jni/src/c/sqlite3-jni.c 6ab1fa79b54b918e63f8ad02b97ed99300b1904ac8542b224fc819a3e7b41c60 +F ext/jni/src/c/sqlite3-jni.h ec38592e88d32f09ba4bde13f2e135bb7cf8712356b807df521b3fc99edeab32 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java b7140bd14563ab831a51feeb17887b3a67175ee19a516f5e040fdbbf6fe6bb2b +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1a7fe4607b80336f0bcb5e0cb7237d987d42c1dfdf59029566c3bc75c2053fbe F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4ba98ec0bf24c31cce498031cb3727e09f928f54ec13c76fec50e439e0f2ba15 -R e261021efec2bf27dec0c1de8bf26411 +P 49005ca5cc191c52279bc7fdb45d95eeb6f8e344f78ce9dbd97aac814bc21202 +R 2e62d651a234281d8736039a6472287a U stephan -Z 2d312267f73405c5ee2bc23c77fa6453 +Z 6b00d1de9eb6d40c30f757066262f00c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 99a825bab5..40daf0cb7f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -49005ca5cc191c52279bc7fdb45d95eeb6f8e344f78ce9dbd97aac814bc21202 \ No newline at end of file +bb8321702eea52fa9d42987a4b053b32d8eba15580a39d7831cd8d6f1ceb62bf \ No newline at end of file From c783fd36dc4acaaa423525297eb67964b034cbfb Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 21:05:39 +0000 Subject: [PATCH 094/148] Add SQLTester dup() and dup_count() UDFs. Correct arg handling of the --run command. FossilOrigin-Name: 0dba3073f44685a51a5db7ff8886295fe04dfd43f69cbf53ad3d5afce741076b --- ext/jni/src/c/sqlite3-jni.c | 95 ++++++++++++++++--- .../src/org/sqlite/jni/tester/SQLTester.java | 15 ++- manifest | 14 +-- manifest.uuid | 2 +- 4 files changed, 100 insertions(+), 26 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index a46429bc58..9cb02cfb4c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -4016,9 +4016,90 @@ JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ #endif /* SQLITE_ENABLE_FTS5 */ //////////////////////////////////////////////////////////////////////// -// End of the main API bindings. What follows are internal utilities. +// End of the main API bindings. Start of SQLTester bits... //////////////////////////////////////////////////////////////////////// +typedef struct SQLTesterJni SQLTesterJni; +struct SQLTesterJni { + sqlite3_int64 nDup; +}; +static SQLTesterJni SQLTester = { + 0 +}; + +static void SQLTester_dup_destructor(void*pToFree){ + u64 *p = (u64*)pToFree; + assert( p!=0 ); + p--; + assert( p[0]==0x2bbf4b7c ); + p[0] = 0; + p[1] = 0; + sqlite3_free(p); +} + +/* +** Implementation of +** +** dup(TEXT) +** +** This SQL function simply makes a copy of its text argument. But it +** returns the result using a custom destructor, in order to provide +** tests for the use of Mem.xDel() in the SQLite VDBE. +*/ +static void SQLTester_dup_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + u64 *pOut; + char *z; + int n = sqlite3_value_bytes(argv[0]); + SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + + ++p->nDup; + if( n>0 && (pOut = sqlite3_malloc( (n+16)&~7 ))!=0 ){ + pOut[0] = 0x2bbf4b7c; + z = (char*)&pOut[1]; + memcpy(z, sqlite3_value_text(argv[0]), n); + z[n] = 0; + sqlite3_result_text(context, z, n, SQLTester_dup_destructor); + } + return; +} + +/* +** Return the number of calls to the dup() SQL function since the +** SQLTester context was opened or since the last dup_count() call. +*/ +static void SQLTester_dup_count_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SQLTesterJni * const p = (SQLTesterJni *)sqlite3_user_data(context); + sqlite3_result_int64(context, p->nDup); + p->nDup = 0; +} + +static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, + const struct sqlite3_api_routines *ignored){ + sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester, + SQLTester_dup_func, 0, 0); + sqlite3_create_function(pDb, "dup_count", 0, SQLITE_UTF8, &SQLTester, + SQLTester_dup_count_func, 0, 0); + return 0; +} + +JNIEXPORT void JNICALL +Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ + sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); +} + +//////////////////////////////////////////////////////////////////////// +// End of SQLTester bindings. Start of lower-level bits. +//////////////////////////////////////////////////////////////////////// + + /** Uncaches the current JNIEnv from the S3JniGlobal state, clearing any resources owned by that cache entry and making that slot available @@ -4030,18 +4111,6 @@ Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ return S3JniGlobal_env_uncache(env) ? JNI_TRUE : JNI_FALSE; } -static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, - const struct sqlite3_api_routines *ignored){ - //MARKER(("TODO: DUP() UDF\n")); - return 0; -} - - -JNIEXPORT void JNICALL -Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ - sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); -} - /** Called during static init of the SQLite3Jni class to sync certain compile-time constants to Java-space. diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 1629bde603..c1995f8577 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -156,9 +156,9 @@ public class SQLTester { int getCurrentDbId(){ return iCurrentDb; } - SQLTester affirmDbId(int n) throws Exception{ + SQLTester affirmDbId(int n) throws IndexOutOfBoundsException { if(n<0 || n>=aDb.length){ - Util.toss(IllegalArgumentException.class,"illegal db number."); + throw new IndexOutOfBoundsException("illegal db number."); } return this; } @@ -169,6 +169,10 @@ public class SQLTester { sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } + sqlite3 getDbById(int id) throws Exception{ + return affirmDbId(id).aDb[iCurrentDb]; + } + void closeDb(int id) throws Exception{ final sqlite3 db = affirmDbId(id).aDb[id]; if( null != db ){ @@ -533,11 +537,12 @@ class ResultCommand extends Command { class RunCommand extends Command { public RunCommand(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0); + argcCheck(argv,0,1); affirmHasContent(content); - int rc = t.execSql(null, false, false, content); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + int rc = t.execSql(db, false, false, content); if( 0!=rc ){ - sqlite3 db = t.getCurrentDb(); String msg = sqlite3_errmsg(db); t.verbose(argv[0]," non-fatal command error #",rc,": ", msg,"\nfor SQL:\n",content); diff --git a/manifest b/manifest index 2b3dc8f488..8d6f499abf 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\s--result\sarg\scount\scheck\sand\sadd\sinfrastructure\sto\slet\sus\sadd\scustom\sC-side\sbehavior\sto\sSQLTester\svia\san\sauto\sextension. -D 2023-08-08T20:41:29.013 +C Add\sSQLTester\sdup()\sand\sdup_count()\sUDFs.\sCorrect\sarg\shandling\sof\sthe\s--run\scommand. +D 2023-08-08T21:05:39.810 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile dcd9595bf9b218c8c97a60f558eaacedbd3901f63ed59475f38c73a7692dd084 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 6ab1fa79b54b918e63f8ad02b97ed99300b1904ac8542b224fc819a3e7b41c60 +F ext/jni/src/c/sqlite3-jni.c 684d22ba352c300118a05918081b7219926469252c904896aa86c3a31025dbe1 F ext/jni/src/c/sqlite3-jni.h ec38592e88d32f09ba4bde13f2e135bb7cf8712356b807df521b3fc99edeab32 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1a7fe4607b80336f0bcb5e0cb7237d987d42c1dfdf59029566c3bc75c2053fbe +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1d54d137405287b7b88b9428134c6208f2c60fb01ad5d0e9126829e4044df1a7 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 49005ca5cc191c52279bc7fdb45d95eeb6f8e344f78ce9dbd97aac814bc21202 -R 2e62d651a234281d8736039a6472287a +P bb8321702eea52fa9d42987a4b053b32d8eba15580a39d7831cd8d6f1ceb62bf +R 20b8372fe27975edee36fa6d84512581 U stephan -Z 6b00d1de9eb6d40c30f757066262f00c +Z 9a23750fa7c3396fc72fc1349e829a3b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 40daf0cb7f..8685b309a3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bb8321702eea52fa9d42987a4b053b32d8eba15580a39d7831cd8d6f1ceb62bf \ No newline at end of file +0dba3073f44685a51a5db7ff8886295fe04dfd43f69cbf53ad3d5afce741076b \ No newline at end of file From 78fc4ae4d9cfb3d62d8911cedb7b4534656af43d Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 21:22:56 +0000 Subject: [PATCH 095/148] Adapt JNI build to be buildable with or without SQLTester. FossilOrigin-Name: adae7d78692af73e770a9cc0a4264ab32ecc18a5c0deb64f3c1e790d959bab43 --- ext/jni/GNUmakefile | 27 ++++++++++++++++++++------- ext/jni/src/c/sqlite3-jni.c | 2 ++ manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 68687d0e32..9d78f9b0e6 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -43,6 +43,8 @@ SQLTester.class := $(SQLTester.java:.java=.class) # The future of FTS5 customization in this API is as yet unclear. # It would be a real doozy to bind to JNI. enable.fts5 ?= 1 +# If enable.tester is 0, the org/sqlite/jni/tester/* bits are elided. +enable.tester ?= 1 # Be explicit about which Java files to compile so that we can work on # in-progress files without requiring them to be in a compilable statae. @@ -90,7 +92,10 @@ JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class) -JAVA_FILES += $(JAVA_FILES.main) $(JAVA_FILES.tester) +JAVA_FILES += $(JAVA_FILES.main) +ifeq (1,$(enable.tester)) + JAVA_FILES += $(JAVA_FILES.tester) +endif CLASS_FILES := define DOTCLASS_DEPS @@ -183,8 +188,10 @@ ifeq (1,$(enable.fts5)) $(eval $(call ADD_JNI_H,fts5_api)) $(eval $(call ADD_JNI_H,fts5_tokenizer)) endif -sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h -$(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java +ifeq (1,$(enable.tester)) + sqlite3-jni.h.in += $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h + $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java +endif #sqlite3-jni.dll.cfiles := $(dir.src.c) sqlite3-jni.dll.cflags := \ -fPIC \ @@ -202,7 +209,9 @@ sqlite3-jni.dll.cflags := \ # subdir which lives under $(JDK_HOME)/include and is a required # include path for client-level code. ######################################################################## - +ifeq (1,$(enable.tester)) + sqlite3-jni.dll.cflags += -DS3JNI_ENABLE_SQLTester +endif $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) cat $(sqlite3-jni.h.in) > $@ $(sqlite3-jni.dll): $(sqlite3-jni.h) $(sqlite3.c) $(sqlite3.h) @@ -219,14 +228,17 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) -.PHONY: tester - tester.flags ?= --verbose - +.PHONY: tester +ifeq (1,$(enable.tester)) tester: $(CLASS_FILES.tester) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts) +else +tester: + @echo "SQLTester support is disabled. Build with enable.tester=1 to enable it." +endif $(package.jar): $(CLASS_FILES) $(MAKEFILE) rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ @@ -236,6 +248,7 @@ jar: $(package.jar) CLEAN_FILES += $(dir.bld.c)/* \ $(dir.src.jni)/*.class \ + $(dir.src.jni.tester)/*.class \ $(sqlite3-jni.dll) \ hs_err_pid*.log diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 9cb02cfb4c..f921a39009 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -4019,6 +4019,7 @@ JDECLFtsXA(jobject,xUserData)(JENV_OSELF,jobject jFcx){ // End of the main API bindings. Start of SQLTester bits... //////////////////////////////////////////////////////////////////////// +#ifdef S3JNI_ENABLE_SQLTester typedef struct SQLTesterJni SQLTesterJni; struct SQLTesterJni { sqlite3_int64 nDup; @@ -4095,6 +4096,7 @@ Java_org_sqlite_jni_tester_SQLTester_installCustomExtensions(JENV_CSELF){ sqlite3_auto_extension( (void(*)(void))SQLTester_auto_extension ); } +#endif /* S3JNI_ENABLE_SQLTester */ //////////////////////////////////////////////////////////////////////// // End of SQLTester bindings. Start of lower-level bits. //////////////////////////////////////////////////////////////////////// diff --git a/manifest b/manifest index 8d6f499abf..dc17de4610 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sSQLTester\sdup()\sand\sdup_count()\sUDFs.\sCorrect\sarg\shandling\sof\sthe\s--run\scommand. -D 2023-08-08T21:05:39.810 +C Adapt\sJNI\sbuild\sto\sbe\sbuildable\swith\sor\swithout\sSQLTester. +D 2023-08-08T21:22:56.734 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,9 +230,9 @@ 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 dcd9595bf9b218c8c97a60f558eaacedbd3901f63ed59475f38c73a7692dd084 +F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 684d22ba352c300118a05918081b7219926469252c904896aa86c3a31025dbe1 +F ext/jni/src/c/sqlite3-jni.c dc1e8678a6acf182c7933cecba2664d760507abbc9af43201d3ac2c14fc64ea0 F ext/jni/src/c/sqlite3-jni.h ec38592e88d32f09ba4bde13f2e135bb7cf8712356b807df521b3fc99edeab32 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P bb8321702eea52fa9d42987a4b053b32d8eba15580a39d7831cd8d6f1ceb62bf -R 20b8372fe27975edee36fa6d84512581 +P 0dba3073f44685a51a5db7ff8886295fe04dfd43f69cbf53ad3d5afce741076b +R 2cf9a6c26ac6b2b58530b5684e479d7d U stephan -Z 9a23750fa7c3396fc72fc1349e829a3b +Z 17c788afc9fee2046b5ea5feb4663dee # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8685b309a3..a17c12f1f4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0dba3073f44685a51a5db7ff8886295fe04dfd43f69cbf53ad3d5afce741076b \ No newline at end of file +adae7d78692af73e770a9cc0a4264ab32ecc18a5c0deb64f3c1e790d959bab43 \ No newline at end of file From 41f94490162ebd214406d523b8fa5abf26332746 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 8 Aug 2023 22:10:27 +0000 Subject: [PATCH 096/148] Remove the current-statement tracking from the JNI internals because it will break down in the face of client-side mixed-mode native/java code, e.g. in cases like SQLTester. This makes tracing of sqlite3_stmt a micron slower but also reliably correct. FossilOrigin-Name: 4c0ec89dca00a9199d1e36768c034aa5eff03b13b5e015cf580f160dc4f141ad --- ext/jni/src/c/sqlite3-jni.c | 61 ++++++++----------------------------- manifest | 12 ++++---- manifest.uuid | 2 +- 3 files changed, 19 insertions(+), 56 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index f921a39009..99eb167510 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -371,15 +371,6 @@ struct S3JniEnvCache { jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; jmethodID stringGetBytes /* the String.getBytes(Charset) method */; } g /* refs to global Java state */; - jobject currentStmt /* Current Java sqlite3_stmt object being - prepared, stepped, reset, or - finalized. Needed for tracing, the - alternative being that we create a new - sqlite3_stmt wrapper object for every tracing - call which needs a stmt object. This approach - is rather invasive, however, requiring code - in all stmt operations which can lead through - the tracing API. */; #ifdef SQLITE_ENABLE_FTS5 jobject jFtsExt /* Global ref to Java singleton for the Fts5ExtensionApi instance. */; @@ -923,7 +914,6 @@ static void S3JniEnvCache_clear(S3JniEnvCache * const p){ UNREF_G(p->g.cLong); UNREF_G(p->g.cString); UNREF_G(p->g.oCharsetUtf8); - UNREF_G(p->currentStmt); #ifdef SQLITE_ENABLE_FTS5 UNREF_G(p->jFtsExt); UNREF_G(p->jPhraseIter.klazz); @@ -2540,30 +2530,12 @@ JDECL(jint,1initialize)(JENV_CSELF){ return sqlite3_initialize(); } -/** - Sets jc->currentStmt to the 2nd arugment and returns the previous - value of jc->currentStmt. This must always be called in pairs: once - to replace the current statement with the call-local one, and once - to restore it. It must be used in any stmt-handling routines which - may lead to the tracing callback being called, as the current stmt - is needed for certain tracing flags. At a minumum those ops are: - step, reset, finalize, prepare. -*/ -static jobject stmt_set_current(S3JniEnvCache * const jc, jobject jStmt){ - jobject const old = jc->currentStmt; - jc->currentStmt = jStmt; - return old; -} - JDECL(jint,1finalize)(JENV_CSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_finalize(pStmt); NativePointerHolder_set(env, jpStmt, 0, S3JniClassNames.sqlite3_stmt); - (void)stmt_set_current(jc, pPrev); } return rc; } @@ -2681,8 +2653,6 @@ static jint sqlite3_jni_prepare_v123(int prepVersion, JNIEnv * const env, jclass jobject jStmt = 0; const char * zTail = 0; jbyte * const pBuf = JBA_TOC(baSql); - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - jobject const pOldStmt = stmt_set_current(jc, 0); int rc = SQLITE_ERROR; assert(prepVersion==1 || prepVersion==2 || prepVersion==3); if( !pBuf ){ @@ -2734,7 +2704,6 @@ end: } #endif OutputPointer_set_sqlite3_stmt(env, jOutStmt, jStmt); - (void)stmt_set_current(jc, pOldStmt); return (jint)rc; } JDECL(jint,1prepare)(JNIEnv * const env, jclass self, jobject jDb, jbyteArray baSql, @@ -2802,10 +2771,7 @@ JDECL(jint,1reset)(JENV_CSELF, jobject jpStmt){ int rc = 0; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); if( pStmt ){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - jobject const pPrev = stmt_set_current(jc, jpStmt); rc = sqlite3_reset(pStmt); - (void)stmt_set_current(jc, pPrev); } return rc; } @@ -3106,10 +3072,7 @@ JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); if(pStmt){ - S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); - jobject const jPrevStmt = stmt_set_current(jc, jStmt); rc = sqlite3_step(pStmt); - (void)stmt_set_current(jc, jPrevStmt); } return rc; } @@ -3122,31 +3085,24 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ jobject jPUnref = NULL /* potentially a local ref to jP */; S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); int rc; + int createStmt = 0; switch(traceflag){ case SQLITE_TRACE_STMT: jX = s3jni_utf8_to_jstring(jc, (const char *)pX, -1); if(!jX) return SQLITE_NOMEM; /*MARKER(("TRACE_STMT@%p SQL=%p / %s\n", pP, jX, (const char *)pX));*/ - jP = jc->currentStmt; + createStmt = 1; break; case SQLITE_TRACE_PROFILE: jX = (*env)->NewObject(env, jc->g.cLong, jc->g.ctorLong1, (jlong)*((sqlite3_int64*)pX)); // hmm. ^^^ (*pX) really is zero. // MARKER(("profile time = %llu\n", *((sqlite3_int64*)pX))); - jP = jc->currentStmt; - if(!jP){ - // This will be the case during prepare() b/c we don't have the - // pointer in time to wrap it before tracing is triggered. - jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); - if(!jP){ - UNREF_L(jX); - return SQLITE_NOMEM; - } - } + if(!jX) return SQLITE_NOMEM; + createStmt = 1; break; case SQLITE_TRACE_ROW: - jP = jc->currentStmt; + createStmt = 1; break; case SQLITE_TRACE_CLOSE: jP = ps->jDb; @@ -3155,6 +3111,13 @@ static int s3jni_trace_impl(unsigned traceflag, void *pC, void *pP, void *pX){ assert(!"cannot happen - unkown trace flag"); return SQLITE_ERROR; } + if( createStmt ){ + jP = jPUnref = new_sqlite3_stmt_wrapper(env, pP); + if(!jP){ + UNREF_L(jX); + return SQLITE_NOMEM; + } + } assert(jP); rc = (int)(*env)->CallIntMethod(env, ps->trace.jObj, ps->trace.midCallback, diff --git a/manifest b/manifest index dc17de4610..28f2f21610 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Adapt\sJNI\sbuild\sto\sbe\sbuildable\swith\sor\swithout\sSQLTester. -D 2023-08-08T21:22:56.734 +C Remove\sthe\scurrent-statement\stracking\sfrom\sthe\sJNI\sinternals\sbecause\sit\swill\sbreak\sdown\sin\sthe\sface\sof\sclient-side\smixed-mode\snative/java\scode,\se.g.\sin\scases\slike\sSQLTester.\sThis\smakes\stracing\sof\ssqlite3_stmt\sa\smicron\sslower\sbut\salso\sreliably\scorrect. +D 2023-08-08T22:10:27.484 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,7 +232,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c dc1e8678a6acf182c7933cecba2664d760507abbc9af43201d3ac2c14fc64ea0 +F ext/jni/src/c/sqlite3-jni.c 07871efe50bb090023259c95df8b851c617917f762b60a3460c9778b2ae6356b F ext/jni/src/c/sqlite3-jni.h ec38592e88d32f09ba4bde13f2e135bb7cf8712356b807df521b3fc99edeab32 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0dba3073f44685a51a5db7ff8886295fe04dfd43f69cbf53ad3d5afce741076b -R 2cf9a6c26ac6b2b58530b5684e479d7d +P adae7d78692af73e770a9cc0a4264ab32ecc18a5c0deb64f3c1e790d959bab43 +R a655364567e3dc78a32998da5ce271ea U stephan -Z 17c788afc9fee2046b5ea5feb4663dee +Z 4250ef5d2c5cf8abb783ee5c1cb8e821 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a17c12f1f4..304f1c6b9e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -adae7d78692af73e770a9cc0a4264ab32ecc18a5c0deb64f3c1e790d959bab43 \ No newline at end of file +4c0ec89dca00a9199d1e36768c034aa5eff03b13b5e015cf580f160dc4f141ad \ No newline at end of file From 23a35c2145ab1c86720855e3e9c8215a6db627b4 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 09:56:37 +0000 Subject: [PATCH 097/148] Some minor tweaks in SQLTester in prep for larger refactoring. FossilOrigin-Name: 1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2 --- .../src/org/sqlite/jni/tester/SQLTester.java | 39 +++++++++++++++---- .../src/org/sqlite/jni/tester/TestScript.java | 5 ++- manifest | 14 +++---- manifest.uuid | 2 +- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index c1995f8577..277065a5c6 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -34,6 +34,19 @@ class SkipTestRemainder extends RuntimeException { } } +/** + Modes for how to handle SQLTester.execSql()'s + result output. + */ +enum ResultBufferMode { + //! Do not append to result buffer + NONE, + //! Append output escaped. + ESCAPED, + //! Append output as-is + ASIS +}; + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -110,14 +123,14 @@ public class SQLTester { ++nTestFile; final TestScript ts = new TestScript(f); currentScript = ts; - outln("---------> Test ",ts.getName()," ..."); + outln("----->>>>> Test [",ts.getName(),"]"); try{ ts.run(this); }catch(SkipTestRemainder e){ /* not an error */ ++nAbortedScript; } - outln("<--------- ",nTest," test(s) in ",f); + outln("<<<<<----- ",nTest," test(s) in [",f,"]"); } }finally{ currentScript = null; @@ -250,7 +263,7 @@ public class SQLTester { } public int execSql(sqlite3 db, boolean throwOnError, - boolean appendToResult, String sql) throws Exception { + ResultBufferMode appendMode, String sql) throws Exception { final OutputPointer.Int32 oTail = new OutputPointer.Int32(); final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); @@ -260,7 +273,8 @@ public class SQLTester { int rc = 0; sqlite3_stmt stmt = null; int spacing = 0 /* emit a space for --result if>0 */ ; - final StringBuilder sb = appendToResult ? resultBuffer : null; + final StringBuilder sb = (ResultBufferMode.NONE==appendMode) + ? null : resultBuffer; //outln("sqlChunk len= = ",sqlChunk.length); while(pos < sqlChunk.length){ if(pos > 0){ @@ -297,7 +311,16 @@ public class SQLTester { sb.append( nullView ); continue; } - sb.append( escapeSqlValue(val) ); + switch(appendMode){ + case ESCAPED: + sb.append( escapeSqlValue(val) ); + break; + case ASIS: + sb.append( val ); + break; + default: + Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + } } //sb.append('\n'); } @@ -453,7 +476,7 @@ class GlobCommand extends Command { t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, true, sql); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, sql); final String result = t.getResultBufferText().trim(); final String sArgs = Util.argvToString(argv); //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); @@ -525,7 +548,7 @@ class ResultCommand extends Command { t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, true, sql); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, sql); final String result = t.getResultBufferText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); @@ -541,7 +564,7 @@ class RunCommand extends Command { affirmHasContent(content); final sqlite3 db = (1==argv.length) ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - int rc = t.execSql(db, false, false, content); + int rc = t.execSql(db, false, ResultBufferMode.NONE, content); if( 0!=rc ){ String msg = sqlite3_errmsg(db); t.verbose(argv[0]," non-fatal command error #",rc,": ", diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index d5af694faa..f5a0134cc9 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -132,7 +132,10 @@ class TestScript { } // Chunk the newly-cleaned text into individual commands and their input... final List rc = new ArrayList<>(); - final Pattern p = Pattern.compile("^--[a-z]", Pattern.MULTILINE); + final Pattern p = Pattern.compile( + "^--(?!end)[a-z]+", Pattern.MULTILINE + // --end is a marker used by --tableresult and --(not)glob. + ); final Matcher m = p.matcher(tmp); int ndxPrev = 0, pos = 0, i = 0; String chunk; diff --git a/manifest b/manifest index 28f2f21610..3d7601d397 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sthe\scurrent-statement\stracking\sfrom\sthe\sJNI\sinternals\sbecause\sit\swill\sbreak\sdown\sin\sthe\sface\sof\sclient-side\smixed-mode\snative/java\scode,\se.g.\sin\scases\slike\sSQLTester.\sThis\smakes\stracing\sof\ssqlite3_stmt\sa\smicron\sslower\sbut\salso\sreliably\scorrect. -D 2023-08-08T22:10:27.484 +C Some\sminor\stweaks\sin\sSQLTester\sin\sprep\sfor\slarger\srefactoring. +D 2023-08-09T09:56:37.905 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,8 +266,8 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1d54d137405287b7b88b9428134c6208f2c60fb01ad5d0e9126829e4044df1a7 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 52350fb458d7d2816377a824c18c498c4a97f0026b64278f62ff1c382a92a070 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d771f9e08f229a6bab80283c4ab5197df5aba9aa09d30e34d13fdc3f35dcbca1 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java e2000ce5db1f2ea23a417bcf6f2ce6ceb93415d81deefce44af5e29dcd7cef7c F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P adae7d78692af73e770a9cc0a4264ab32ecc18a5c0deb64f3c1e790d959bab43 -R a655364567e3dc78a32998da5ce271ea +P 4c0ec89dca00a9199d1e36768c034aa5eff03b13b5e015cf580f160dc4f141ad +R 4e458d29aca7fb31fe732db9fc592362 U stephan -Z 4250ef5d2c5cf8abb783ee5c1cb8e821 +Z cd7ae073790f5058633c4b99263e9219 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 304f1c6b9e..8826aaa5d8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4c0ec89dca00a9199d1e36768c034aa5eff03b13b5e015cf580f160dc4f141ad \ No newline at end of file +1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2 \ No newline at end of file From e6c29da77795f079fca51856f61c701d2dca9795 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 11:05:43 +0000 Subject: [PATCH 098/148] Rework how SQLTester's Command objects are dispatched and how TestScript stores its command entries. FossilOrigin-Name: f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de --- .../src/org/sqlite/jni/tester/SQLTester.java | 186 +++++++++++------- .../src/org/sqlite/jni/tester/TestScript.java | 79 ++++---- ext/jni/src/tests/000_first.test | 8 +- manifest | 16 +- manifest.uuid | 2 +- 5 files changed, 172 insertions(+), 119 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 277065a5c6..e38a9877b4 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -124,11 +124,17 @@ public class SQLTester { final TestScript ts = new TestScript(f); currentScript = ts; outln("----->>>>> Test [",ts.getName(),"]"); - try{ - ts.run(this); - }catch(SkipTestRemainder e){ - /* not an error */ - ++nAbortedScript; + if( ts.isIgnored() ){ + outln("WARNING: skipping [",ts.getName(),"] because it contains ", + "content which requires that it be skipped."); + continue; + }else{ + try{ + ts.run(this); + }catch(SkipTestRemainder e){ + /* not an error */ + ++nAbortedScript; + } } outln("<<<<<----- ",nTest," test(s) in [",f,"]"); } @@ -385,37 +391,55 @@ public class SQLTester { Base class for test script commands. It provides a set of utility APIs for concrete command implementations. - Each subclass must have a ctor with this signature: + Each subclass must have a public no-arg ctor and must implement + the process() method which is abstract in this class. - (SQLTester testContext, String[] argv, String content) throws Exception - - argv is a list with the command name followed by any - arguments to that command. The argcCheck() method provides - very basic argc validation. - - The content is any text content which was specified after the - command, or null if there is null. Any command which does not - permit content must pass that argument to affirmNoContent() in - their constructor. Similary, those which require content should - pass it to affirmHasContent(). - - For simplicity, instantiating the test is intended to execute it, - as opposed to delaying execution until a method devoted to that. - - Tests must throw on error. + Commands are intended to be stateless, except perhaps for counters + and similar internals. No state which changes the behavior between + any two invocations of process() should be retained. */ -class Command { +abstract class Command { protected Command(){} + /** + Must process one command-unit of work and either return + (on success) or throw (on error). + + The first argument is the context of the test. + + argv is a list with the command name followed by any arguments to + that command. The argcCheck() method from this class provides + very basic argc validation. + + The content is any text content which was specified after the + command, or null if there is null. Any command which does not + permit content must pass that argument to affirmNoContent() in + their constructor (or perform an equivalent check). Similary, + those which require content must pass it to affirmHasContent() + (or equivalent). + */ + public abstract void process(SQLTester tester, String[] argv, String content) throws Exception; + + /** + If argv.length-1 (-1 because the command's name is in argv[0]) does not + fall in the inclusive range (min,max) then this function throws. Use + a max value of -1 to mean unlimited. + */ protected final void argcCheck(String[] argv, int min, int max) throws Exception{ int argc = argv.length-1; - if(max<0) max = 99999999; - if(argcmax){ + if(argc=0 && argc>max)){ if( min==max ) Util.badArg(argv[0],"requires exactly",min,"argument(s)"); - else Util.badArg(argv[0]," requires ",min,"-",max," arguments."); + else if(max>0){ + Util.badArg(argv[0]," requires ",min,"-",max," arguments."); + }else{ + Util.badArg(argv[0]," requires at least ",min," arguments."); + } } } + /** + Equivalent to argcCheck(argv,argc,argc). + */ protected final void argcCheck(String[] argv, int argc) throws Exception{ argcCheck(argv, argc, argc); } @@ -436,7 +460,8 @@ class Command { } class CloseDbCommand extends Command { - public CloseDbCommand(SQLTester t, String[] argv, String content) throws Exception{ + public CloseDbCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,1); affirmNoContent(content); Integer id; @@ -458,8 +483,10 @@ class CloseDbCommand extends Command { } } +//! --db command class DbCommand extends Command { - public DbCommand(SQLTester t, String[] argv, String content) throws Exception{ + public DbCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); final sqlite3 db = t.setCurrentDb( Integer.parseInt(argv[1]) ); @@ -467,9 +494,13 @@ class DbCommand extends Command { } } +//! --glob command class GlobCommand extends Command { - protected GlobCommand(boolean negate, SQLTester t, - String[] argv, String content) throws Exception{ + private boolean negate = false; + public GlobCommand(){} + protected GlobCommand(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -487,13 +518,12 @@ class GlobCommand extends Command { " glob mismatch: ",glob," vs input: ",result); } } - public GlobCommand(SQLTester t, String[] argv, String content) throws Exception{ - this(false, t, argv, content); - } } +//! --new command class NewDbCommand extends Command { - public NewDbCommand(SQLTester t, String[] argv, String content) throws Exception{ + public NewDbCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); String fname = argv[1]; @@ -503,19 +533,24 @@ class NewDbCommand extends Command { } } +//! Placeholder dummy/no-op command class NoopCommand extends Command { - public NoopCommand(SQLTester t, String[] argv, String content) throws Exception{ + public NoopCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ } } +//! --notglob command class NotGlobCommand extends GlobCommand { - public NotGlobCommand(SQLTester t, String[] argv, String content) throws Exception{ - super(true, t, argv, content); + public NotGlobCommand(){ + super(true); } } +//! --null command class NullCommand extends Command { - public NullCommand(SQLTester t, String[] argv, String content) throws Exception{ + public NullCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); t.setNullValue(argv[1]); @@ -523,8 +558,10 @@ class NullCommand extends Command { } } +//! --open command class OpenDbCommand extends Command { - public OpenDbCommand(SQLTester t, String[] argv, String content) throws Exception{ + public OpenDbCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); String fname = argv[1]; @@ -533,16 +570,18 @@ class OpenDbCommand extends Command { } } - +//! --print command class PrintCommand extends Command { - public PrintCommand(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0); - t.outln(content); + public PrintCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ + if( argv.length > 1 ) t.outln("\t",Util.argvToString(argv)); + if( null!=content ) t.outln(content.replaceAll("(?m)^", "\t")); } } class ResultCommand extends Command { - public ResultCommand(SQLTester t, String[] argv, String content) throws Exception{ + public ResultCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,-1); affirmNoContent(content); t.incrementTestCounter(); @@ -559,7 +598,8 @@ class ResultCommand extends Command { } class RunCommand extends Command { - public RunCommand(SQLTester t, String[] argv, String content) throws Exception{ + public RunCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,1); affirmHasContent(content); final sqlite3 db = (1==argv.length) @@ -574,7 +614,8 @@ class RunCommand extends Command { } class TestCaseCommand extends Command { - public TestCaseCommand(SQLTester t, String[] argv, String content) throws Exception{ + public TestCaseCommand(){} + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmHasContent(content); // TODO: do something with the test name @@ -586,28 +627,37 @@ class TestCaseCommand extends Command { class CommandDispatcher { - static Class getCommandByName(String name){ + private static java.util.Map commandMap = + new java.util.HashMap<>(); + static Command getCommandByName(String name){ + // TODO? Do this dispatching using a custom annotation on + // Command impls. That requires a surprisingly huge amount + // of code, though. + Command rv = commandMap.get(name); + if( null!=rv ) return rv; switch(name){ - case "close": return CloseDbCommand.class; - case "db": return DbCommand.class; - case "glob": return GlobCommand.class; - case "new": return NewDbCommand.class; - case "notglob": return NotGlobCommand.class; - case "null": return NullCommand.class; - case "oom": return NoopCommand.class; - case "open": return OpenDbCommand.class; - case "print": return PrintCommand.class; - case "result": return ResultCommand.class; - case "run": return RunCommand.class; - case "testcase": return TestCaseCommand.class; - default: return null; + case "close": rv = new CloseDbCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + default: rv = null; break; } + if( null!=rv ) commandMap.put(name, rv); + return rv; } @SuppressWarnings("unchecked") static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{ - final Class cmdClass = getCommandByName(argv[0]); - if(null == cmdClass){ + final Command cmd = getCommandByName(argv[0]); + if(null == cmd){ final TestScript ts = tester.getCurrentScript(); if( tester.skipUnknownCommands() ){ tester.outln("WARNING: skipping remainder of ",ts.getName(), @@ -618,15 +668,8 @@ class CommandDispatcher { "No command handler found for '"+argv[0]+"' in ", ts.getName()); } - final java.lang.reflect.Constructor ctor = - cmdClass.getConstructor(SQLTester.class, String[].class, String.class); - try{ - //tester.verbose("Running ",argv[0]," with:\n", content); - ctor.newInstance(tester, argv, content); - }catch(java.lang.reflect.InvocationTargetException e){ - Throwable t = e.getCause(); - throw (t!=null && t instanceof Exception) ? (Exception)t : e; - } + //tester.verbose("Running ",argv[0]," with:\n", content); + cmd.process(tester, argv, content); } } @@ -664,4 +707,5 @@ final class Util { } return sb.toString(); } + } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index f5a0134cc9..96b372e8ae 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -18,26 +18,30 @@ import java.io.*; import java.util.regex.*; /** - This class represents a single test script. It handles (or delegates) - its input and parsing. Iteration and evalution are deferred to other, - as-yet-non-existent, classes. - + This class represents a single test script. It handles (or + delegates) its the reading-in and parsing, but the details of + evaluation are delegated elsewhere. */ class TestScript { private String name; - private String content; - private List chunks = null; + private List chunks = null; private final Outer outer = new Outer(); private boolean ignored = false; + /* One "chunk" of input, representing a single command and + its optional body content. */ + private static final class CommandChunk { + public String[] argv = null; + public String content = null; + } + private byte[] readFile(String filename) throws Exception { return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); } private void setContent(String c){ - content = c; ignored = shouldBeIgnored(c); - chunks = chunkContent(); + if( !ignored ) chunks = chunkContent(c); } /** Initializes the script with the content of the given file. @@ -104,7 +108,7 @@ class TestScript { If/when that becomes a problem, it can be refactored. */ - private List chunkContent(){ + private List chunkContent(String content){ if( ignored ) return null; // First, strip out any content which we know we can ignore... final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; @@ -117,53 +121,63 @@ class TestScript { lPats.add(sTclComment); lPats.add(sEmptyLine); //verbose("Content:").verbose(content).verbose(""); - String tmp = content; for( String s : lPats ){ final Pattern p = Pattern.compile( s, Pattern.MULTILINE ); - final Matcher m = p.matcher(tmp); + final Matcher m = p.matcher(content); /*verbose("Pattern {{{ ",p.pattern()," }}} with flags ", p.flags()," matches:" );*/ int n = 0; //while( m.find() ) verbose("#",(++n),"\t",m.group(0).trim()); - tmp = m.replaceAll(""); + content = m.replaceAll(""); } // Chunk the newly-cleaned text into individual commands and their input... - final List rc = new ArrayList<>(); + // First split up the input into command-size blocks... + final List blocks = new ArrayList<>(); final Pattern p = Pattern.compile( "^--(?!end)[a-z]+", Pattern.MULTILINE // --end is a marker used by --tableresult and --(not)glob. ); - final Matcher m = p.matcher(tmp); + final Matcher m = p.matcher(content); int ndxPrev = 0, pos = 0, i = 0; - String chunk; - //verbose("Trimmed content:").verbose(tmp).verbose(""); + //verbose("Trimmed content:").verbose(content).verbose(""); while( m.find() ){ pos = m.start(); - chunk = tmp.substring(ndxPrev, pos).trim(); + final String block = content.substring(ndxPrev, pos).trim(); if( 0==ndxPrev && pos>ndxPrev ){ - /* Initial chunk of non-command state. Skip it. */ + /* Initial block of non-command state. Skip it. */ ndxPrev = pos + 2; continue; } - if( !chunk.isEmpty() ){ + if( !block.isEmpty() ){ ++i; - //verbose("CHUNK #",i," ",+ndxPrev,"..",pos,chunk); - rc.add( chunk ); + //verbose("BLOCK #",i," ",+ndxPrev,"..",pos,block); + blocks.add( block ); } ndxPrev = pos + 2; } - if( ndxPrev < tmp.length() ){ + if( ndxPrev < content.length() ){ // This all belongs to the final command - chunk = tmp.substring(ndxPrev, tmp.length()).trim(); - if( !chunk.isEmpty() ){ + final String block = content.substring(ndxPrev, content.length()).trim(); + if( !block.isEmpty() ){ ++i; - //verbose("CHUNK #",(++i)," ",chunk); - rc.add( chunk ); + //verbose("BLOCK #",(++i)," ",block); + blocks.add( block ); } } + // Next, convert those blocks into higher-level CommandChunks... + final List rc = new ArrayList<>(); + for( String block : blocks ){ + final CommandChunk chunk = new CommandChunk(); + final String[] parts = block.split("\\n", 2); + chunk.argv = parts[0].split("\\s+"); + if( parts.length>1 && parts[1].length()>0 ){ + chunk.content = parts[1]; + } + rc.add( chunk ); + } return rc; } @@ -175,15 +189,10 @@ class TestScript { if( null==chunks ){ outer.outln("This test contains content which forces it to be skipped."); }else{ - int n = 0; - for(String chunk : chunks){ - ++n; - //outer.verbose("CHUNK #",n," ",chunk,""); - final String[] parts = chunk.split("\\n", 2); - final String[] argv = parts[0].split("\\s+"); - CommandDispatcher.dispatch( - tester, argv, parts.length>1 ? parts[1] : null - ); + //int n = 0; + for(CommandChunk chunk : chunks){ + //outer.verbose("CHUNK #",++n," ",chunk,""); + CommandDispatcher.dispatch(tester, chunk.argv, chunk.content); } } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 859a634ca0..fe3d35b045 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -16,8 +16,9 @@ select 2; -- comment -- uncomment to introduce intentional syntax error --oom ---print -This is from the print command. +--print These are args to the print command. +This is from the print command's body. +Also from the print command. --- also ignored --testcase first select 'a', 'b'; @@ -31,6 +32,5 @@ select 'a' --notglob # --close --open SQLTester.db ---print -Re-opened db. +--print Re-opened db. --an-uknown-command diff --git a/manifest b/manifest index 3d7601d397..eec0b5d260 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Some\sminor\stweaks\sin\sSQLTester\sin\sprep\sfor\slarger\srefactoring. -D 2023-08-09T09:56:37.905 +C Rework\show\sSQLTester's\sCommand\sobjects\sare\sdispatched\sand\show\sTestScript\sstores\sits\scommand\sentries. +D 2023-08-09T11:05:43.390 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d771f9e08f229a6bab80283c4ab5197df5aba9aa09d30e34d13fdc3f35dcbca1 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java e2000ce5db1f2ea23a417bcf6f2ce6ceb93415d81deefce44af5e29dcd7cef7c +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 3ab7c123fcd85932c0c5082419d20d284bd0520755b777bc68d6eb16950d35f9 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9d9e60cf62eb66d4c3b1567c03b84f5354c72605bf826d4375a6831ff53ba66b F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c -F ext/jni/src/tests/000_first.test bd912c4d88f4f85264de1b53267114891bdb4c6d0d2e847343bc3ff482ec296e +F ext/jni/src/tests/000_first.test e4f643e631567a67a21cd9406c13b031580f9703079490480c7bbd1cae421183 F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4c0ec89dca00a9199d1e36768c034aa5eff03b13b5e015cf580f160dc4f141ad -R 4e458d29aca7fb31fe732db9fc592362 +P 1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2 +R 51876d83c95ab59b5aaaafa4581c6850 U stephan -Z cd7ae073790f5058633c4b99263e9219 +Z ebf66076159eae05ac15c85362d3eee5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8826aaa5d8..2092ba70d8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2 \ No newline at end of file +f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de \ No newline at end of file From 283e87146cee6ec56f2e8516d07db5d7736ded8e Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 11:10:48 +0000 Subject: [PATCH 099/148] Document SQLTester's --print command and add some argument validation to it. FossilOrigin-Name: ab9c945bb0b4210b3f47e6341f150f8a7cc45f9e4e4c2247e91d2528ed4772a6 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 3 +++ .../sqlite/jni/tester/test-script-interpreter.md | 5 +++++ ext/jni/src/tests/000_first.test | 1 + manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index e38a9877b4..574ef38737 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -574,6 +574,9 @@ class OpenDbCommand extends Command { class PrintCommand extends Command { public PrintCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ + if( 1==argv.length && null==content ){ + Util.badArg(argv[0]," requires at least 1 argument or body content."); + } if( argv.length > 1 ) t.outln("\t",Util.argvToString(argv)); if( null!=content ) t.outln(content.replaceAll("(?m)^", "\t")); } diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index ec1ea53005..440a7e0173 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -244,3 +244,8 @@ respectively. The difference is that column values are appended to the result buffer literally, without every enclosing the values in `{...}` or `"..."` and without escaping any characters in the column value and comparison is always an exact strcmp() not a GLOB. + +### The --print command + +The --print command emits both its arguments and its body (if any) to +stdout, indenting each line of output. diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index fe3d35b045..77bc8af4fa 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -18,6 +18,7 @@ select 2; --oom --print These are args to the print command. This is from the print command's body. +--print Also from the print command. --- also ignored --testcase first diff --git a/manifest b/manifest index eec0b5d260..d0d95406b8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Rework\show\sSQLTester's\sCommand\sobjects\sare\sdispatched\sand\show\sTestScript\sstores\sits\scommand\sentries. -D 2023-08-09T11:05:43.390 +C Document\sSQLTester's\s--print\scommand\sand\sadd\ssome\sargument\svalidation\sto\sit. +D 2023-08-09T11:10:48.778 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 3ab7c123fcd85932c0c5082419d20d284bd0520755b777bc68d6eb16950d35f9 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 6f2c6679cc3f334058b364de88cf6817ce111514514abdc4fd70c6805c94d824 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9d9e60cf62eb66d4c3b1567c03b84f5354c72605bf826d4375a6831ff53ba66b -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 4a4868c70a68aa1829c1f7659daa78198187199d176778efb86a239c9e58802c -F ext/jni/src/tests/000_first.test e4f643e631567a67a21cd9406c13b031580f9703079490480c7bbd1cae421183 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 55bef0dc8580b90fa13e3728829d5e585b74f50d6ae35b9999bdef5aca0a7cab +F ext/jni/src/tests/000_first.test 8bfd5d94fc51586461bdb311ff8df4e772555a29c3babc9e01ad0de324638c1e F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 1d93f93ac9708839e62d2f1b489adc5d47ff290c2d5aef4dd56be4e1e46c81b2 -R 51876d83c95ab59b5aaaafa4581c6850 +P f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de +R e5314da4aee7d756c10ad57fb9cc922a U stephan -Z ebf66076159eae05ac15c85362d3eee5 +Z 8a5463f5ea02a18c2733745eccce2961 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2092ba70d8..356da73a0a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de \ No newline at end of file +ab9c945bb0b4210b3f47e6341f150f8a7cc45f9e4e4c2247e91d2528ed4772a6 \ No newline at end of file From aec9aa928979ace87f1f5ecc14a5f7dec494faed Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 12:05:17 +0000 Subject: [PATCH 100/148] Clean up the SQLTester output a bit by using the module name, instead of filename, where appropriate. FossilOrigin-Name: 5323e4fd254274cc527af7536c622b786394599c68eca2da6c7fc641727dbdb2 --- .../src/org/sqlite/jni/tester/SQLTester.java | 10 ++--- .../src/org/sqlite/jni/tester/TestScript.java | 41 ++++++++++++++----- .../jni/tester/test-script-interpreter.md | 2 +- ext/jni/src/tests/000_first.test | 1 + ext/jni/src/tests/010_ignored.test | 9 +++- manifest | 20 ++++----- manifest.uuid | 2 +- 7 files changed, 56 insertions(+), 29 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 574ef38737..0fd80c7fa6 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -123,9 +123,9 @@ public class SQLTester { ++nTestFile; final TestScript ts = new TestScript(f); currentScript = ts; - outln("----->>>>> Test [",ts.getName(),"]"); + outln("----->>>>> ",ts.getModuleName()," [",ts.getName(),"]"); if( ts.isIgnored() ){ - outln("WARNING: skipping [",ts.getName(),"] because it contains ", + outln("WARNING: skipping [",ts.getModuleName(),"] because it contains ", "content which requires that it be skipped."); continue; }else{ @@ -136,7 +136,7 @@ public class SQLTester { ++nAbortedScript; } } - outln("<<<<<----- ",nTest," test(s) in [",f,"]"); + outln("<<<<<----- ",ts.getModuleName(),": ",nTest," test(s)"); } }finally{ currentScript = null; @@ -663,8 +663,8 @@ class CommandDispatcher { if(null == cmd){ final TestScript ts = tester.getCurrentScript(); if( tester.skipUnknownCommands() ){ - tester.outln("WARNING: skipping remainder of ",ts.getName(), - " because it contains unknown command '",argv[0],"'."); + tester.outln("WARNING: skipping remainder of [",ts.getModuleName(), + "] because it contains unknown command '",argv[0],"'."); throw new SkipTestRemainder(ts); } Util.toss(IllegalArgumentException.class, diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 96b372e8ae..0bc7b53e77 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -23,7 +23,8 @@ import java.util.regex.*; evaluation are delegated elsewhere. */ class TestScript { - private String name; + private String name = null; + private String moduleName = null; private List chunks = null; private final Outer outer = new Outer(); private boolean ignored = false; @@ -39,18 +40,14 @@ class TestScript { return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); } - private void setContent(String c){ - ignored = shouldBeIgnored(c); - if( !ignored ) chunks = chunkContent(c); - } /** Initializes the script with the content of the given file. Throws if it cannot read the file or if tokenizing it fails. */ public TestScript(String filename) throws Exception{ + name = filename; setContent(new String(readFile(filename), java.nio.charset.StandardCharsets.UTF_8)); - name = filename; } /** @@ -61,14 +58,22 @@ class TestScript { */ public TestScript(String virtualName, StringBuffer content) throws RuntimeException { - setContent(content.toString()); name = virtualName; + setContent(content.toString()); + } + + private void setContent(String c){ + this.chunks = chunkContent(c); } public String getName(){ return name; } + public String getModuleName(){ + return moduleName; + } + public boolean isIgnored(){ return ignored; } @@ -87,11 +92,21 @@ class TestScript { Returns true if the given script content should be ignored (because it contains certain content which indicates such). */ - public static boolean shouldBeIgnored(String content){ - return content.indexOf("SCRIPT_MODULE_NAME")<0 + public boolean shouldBeIgnored(String content){ + return (null == moduleName) || content.indexOf("\n|")>=0; } + private boolean findModuleName(String content){ + final Pattern p = Pattern.compile( + "SCRIPT_MODULE_NAME:\\s+(\\S+)\\s*\n", + Pattern.MULTILINE + ); + final Matcher m = p.matcher(content); + moduleName = m.find() ? m.group(1) : null; + return moduleName != null; + } + /** Chop script up into chunks containing individual commands and their inputs. The approach taken here is not as robust as @@ -109,7 +124,13 @@ class TestScript { If/when that becomes a problem, it can be refactored. */ private List chunkContent(String content){ - if( ignored ) return null; + findModuleName(content); + ignored = shouldBeIgnored(content); + if( ignored ){ + chunks = null; + return null; + } + // First, strip out any content which we know we can ignore... final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; final String s3Dash = "^---+[^\\n]*\\n"; diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 440a7e0173..4f0052fc11 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -212,7 +212,7 @@ which database connection to use moving forward. ### The --close command -The --close command causes an existing database connetion to close. +The --close command causes an existing database connection to close. This command is a no-op if the database connection is not currently open. There can be up to 7 different database connections, numbered 0 through 6. The number of the database connection to close is an diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 77bc8af4fa..3f61317b90 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -8,6 +8,7 @@ junk +# --open nope.db /* must throw */ --new SQLTester.db --null zilch --run diff --git a/ext/jni/src/tests/010_ignored.test b/ext/jni/src/tests/010_ignored.test index fe15c547c5..5af852e197 100644 --- a/ext/jni/src/tests/010_ignored.test +++ b/ext/jni/src/tests/010_ignored.test @@ -1,4 +1,9 @@ -/* This script must be marked as ignored because it contains - content which triggers that condition. */ +/* +** This script must be marked as ignored because it contains +** content which triggers that condition. +** +** SCRIPT_MODULE_NAME: ignored +** +*/ | diff --git a/manifest b/manifest index d0d95406b8..3165e6822e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Document\sSQLTester's\s--print\scommand\sand\sadd\ssome\sargument\svalidation\sto\sit. -D 2023-08-09T11:10:48.778 +C Clean\sup\sthe\sSQLTester\soutput\sa\sbit\sby\susing\sthe\smodule\sname,\sinstead\sof\sfilename,\swhere\sappropriate. +D 2023-08-09T12:05:17.196 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,11 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 6f2c6679cc3f334058b364de88cf6817ce111514514abdc4fd70c6805c94d824 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9d9e60cf62eb66d4c3b1567c03b84f5354c72605bf826d4375a6831ff53ba66b -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 55bef0dc8580b90fa13e3728829d5e585b74f50d6ae35b9999bdef5aca0a7cab -F ext/jni/src/tests/000_first.test 8bfd5d94fc51586461bdb311ff8df4e772555a29c3babc9e01ad0de324638c1e -F ext/jni/src/tests/010_ignored.test ce2de6742ff1bf98d8976fda0f260ff3d280e8f8c0a99309fb59fcfef2556fcd +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 42b694da25e20a246140e32d8aa044e65ed60c67f69adcf27c326a1d18b04228 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 57a5bb63e56324fe20b31142a8704b08cfc0bdff9e936620346fad659fb91759 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ae1d6706f723517e03a04ab578a539fa3df66fe38adad113f10b61eabc524d09 +F ext/jni/src/tests/000_first.test 954c19705c791023eb5a473de0851d3727406fdef25f4b2521b88972280b4111 +F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f929f1f7f70181813f74562614f3f2aa29e65590560e3fce1677b8b176e3c6de -R e5314da4aee7d756c10ad57fb9cc922a +P ab9c945bb0b4210b3f47e6341f150f8a7cc45f9e4e4c2247e91d2528ed4772a6 +R 71db74e5804fc1b921164f2085cce4db U stephan -Z 8a5463f5ea02a18c2733745eccce2961 +Z d1b5b5a17f205dc2910f24396b36f01b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 356da73a0a..7c80c48420 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ab9c945bb0b4210b3f47e6341f150f8a7cc45f9e4e4c2247e91d2528ed4772a6 \ No newline at end of file +5323e4fd254274cc527af7536c622b786394599c68eca2da6c7fc641727dbdb2 \ No newline at end of file From ff6b15fbb2e1c78ce99f1555c8bb5b364fcfdec7 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 13:16:10 +0000 Subject: [PATCH 101/148] Add SQLTester --tableresult command. FossilOrigin-Name: 8c5b6d893df4a4e82c6d8e07507fc160b11412ede4bb903ff4e3f5ffa59a9cb9 --- .../src/org/sqlite/jni/tester/SQLTester.java | 100 +++++++++++++----- .../src/org/sqlite/jni/tester/TestScript.java | 2 +- ext/jni/src/tests/000_first.test | 11 +- manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 92 insertions(+), 39 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 0fd80c7fa6..e4d21f88d0 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -47,6 +47,13 @@ enum ResultBufferMode { ASIS }; +enum ResultRowMode { + //! Keep all result rows on one line, space-separated. + ONELINE, + //! Add a newline between each result row. + NEWLINE +}; + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -159,9 +166,9 @@ public class SQLTester { StringBuilder getInputBuffer(){ return inputBuffer; } - String getInputBufferText(){ return inputBuffer.toString(); } + String getInputText(){ return inputBuffer.toString(); } - String getResultBufferText(){ return resultBuffer.toString(); } + String getResultText(){ return resultBuffer.toString(); } private String takeBuffer(StringBuilder b){ final String rc = b.toString(); @@ -269,7 +276,9 @@ public class SQLTester { } public int execSql(sqlite3 db, boolean throwOnError, - ResultBufferMode appendMode, String sql) throws Exception { + ResultBufferMode appendMode, + ResultRowMode lineMode, + String sql) throws Exception { final OutputPointer.Int32 oTail = new OutputPointer.Int32(); final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); @@ -328,7 +337,10 @@ public class SQLTester { Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); } } - //sb.append('\n'); + if( ResultRowMode.NEWLINE == lineMode ){ + spacing = 0; + sb.append('\n'); + } } }else{ while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} @@ -428,8 +440,9 @@ abstract class Command { protected final void argcCheck(String[] argv, int min, int max) throws Exception{ int argc = argv.length-1; if(argc=0 && argc>max)){ - if( min==max ) Util.badArg(argv[0],"requires exactly",min,"argument(s)"); - else if(max>0){ + if( min==max ){ + Util.badArg(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ Util.badArg(argv[0]," requires ",min,"-",max," arguments."); }else{ Util.badArg(argv[0]," requires at least ",min," arguments."); @@ -447,20 +460,19 @@ abstract class Command { //! Throws if content is not null. protected void affirmNoContent(String content) throws Exception{ if(null != content){ - Util.badArg(this.getClass().getName(),"does not accept content."); + Util.badArg(this.getClass().getName()," does not accept content."); } } //! Throws if content is null. protected void affirmHasContent(String content) throws Exception{ if(null == content){ - Util.badArg(this.getClass().getName(),"requires content."); + Util.badArg(this.getClass().getName()," requires content."); } } } class CloseDbCommand extends Command { - public CloseDbCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,1); affirmNoContent(content); @@ -485,7 +497,6 @@ class CloseDbCommand extends Command { //! --db command class DbCommand extends Command { - public DbCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -500,6 +511,13 @@ class GlobCommand extends Command { public GlobCommand(){} protected GlobCommand(boolean negate){ this.negate = negate; } + public static String globToStrglob(String g){ + /* FIXME: '#' support needs to match 1+ digits, but + sqlite3_strglob() does not support that option. We'll + need a custom glob routine for that. */; + return g.replace("#","[0-9]").trim(); + } + public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -507,22 +525,22 @@ class GlobCommand extends Command { t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, sql); - final String result = t.getResultBufferText().trim(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); final String sArgs = Util.argvToString(argv); //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = argv[1].replace("#","[0-9]"); + final String glob = globToStrglob(argv[1]); rc = sqlite3_strglob(glob, result); if( (negate && 0==rc) || (!negate && 0!=rc) ){ - Util.toss(TestFailure.class, this.getClass().getSimpleName(), - " glob mismatch: ",glob," vs input: ",result); + Util.toss(TestFailure.class, argv[0], " mismatch: ", + glob," vs input: ",result); } } } //! --new command class NewDbCommand extends Command { - public NewDbCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -535,7 +553,6 @@ class NewDbCommand extends Command { //! Placeholder dummy/no-op command class NoopCommand extends Command { - public NoopCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ } } @@ -549,7 +566,6 @@ class NotGlobCommand extends GlobCommand { //! --null command class NullCommand extends Command { - public NullCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -560,7 +576,6 @@ class NullCommand extends Command { //! --open command class OpenDbCommand extends Command { - public OpenDbCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -572,7 +587,6 @@ class OpenDbCommand extends Command { //! --print command class PrintCommand extends Command { - public PrintCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ if( 1==argv.length && null==content ){ Util.badArg(argv[0]," requires at least 1 argument or body content."); @@ -583,15 +597,15 @@ class PrintCommand extends Command { } class ResultCommand extends Command { - public ResultCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,-1); affirmNoContent(content); t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, sql); - final String result = t.getResultBufferText().trim(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); if( !result.equals(sArgs) ){ @@ -601,14 +615,14 @@ class ResultCommand extends Command { } class RunCommand extends Command { - public RunCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,1); affirmHasContent(content); final sqlite3 db = (1==argv.length) ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - int rc = t.execSql(db, false, ResultBufferMode.NONE, content); - if( 0!=rc ){ + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, content); + if( 0!=rc && t.isVerbose() ){ String msg = sqlite3_errmsg(db); t.verbose(argv[0]," non-fatal command error #",rc,": ", msg,"\nfor SQL:\n",content); @@ -616,8 +630,39 @@ class RunCommand extends Command { } } +class TableResultCommand extends Command { + public void process(SQLTester t, String[] argv, String content) throws Exception{ + argcCheck(argv,0); + affirmHasContent(content); + if( !content.endsWith("\n--end") ){ + Util.toss(TestFailure.class, argv[0], " must be terminated with --end."); + }else{ + int n = content.length(); + content = content.substring(0, n-6); + } + final String[] globs = content.split("\s*\n\s*"); + if( globs.length < 1 ){ + Util.toss(TestFailure.class, argv[0], " requires 1 or more globs."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, ResultBufferMode.ESCAPED, ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + Util.toss(TestFailure.class, argv[0], " failure: input has ", + res.length," row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = GlobCommand.globToStrglob(globs[i]).replaceAll("\s+"," "); + if( 0 != sqlite3_strglob(glob, res[i]) ){ + Util.toss(TestFailure.class, argv[0], " glob {",glob, + "} does not match: {",res[i],"}"); + } + } + } +} + class TestCaseCommand extends Command { - public TestCaseCommand(){} public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmHasContent(content); @@ -650,6 +695,7 @@ class CommandDispatcher { case "print": rv = new PrintCommand(); break; case "result": rv = new ResultCommand(); break; case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; case "testcase": rv = new TestCaseCommand(); break; default: rv = null; break; } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 0bc7b53e77..414ea29bc1 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -195,7 +195,7 @@ class TestScript { final String[] parts = block.split("\\n", 2); chunk.argv = parts[0].split("\\s+"); if( parts.length>1 && parts[1].length()>0 ){ - chunk.content = parts[1]; + chunk.content = parts[1].trim(); } rc.add( chunk ); } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 3f61317b90..742ebfa451 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -28,11 +28,18 @@ select 'a', 'b'; --result a b a b --testcase second select 123 ---glob #2# ---testcase second +--glob ### +--testcase third select 'a' --notglob # --close --open SQLTester.db --print Re-opened db. +--testcase fourth +SELECT 1, 2; +SELECT 'b', 'c'; +--tableresult + [0-9] # + b c +--end --an-uknown-command diff --git a/manifest b/manifest index 3165e6822e..7f72a5fc63 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Clean\sup\sthe\sSQLTester\soutput\sa\sbit\sby\susing\sthe\smodule\sname,\sinstead\sof\sfilename,\swhere\sappropriate. -D 2023-08-09T12:05:17.196 +C Add\sSQLTester\s--tableresult\scommand. +D 2023-08-09T13:16:10.208 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 42b694da25e20a246140e32d8aa044e65ed60c67f69adcf27c326a1d18b04228 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 57a5bb63e56324fe20b31142a8704b08cfc0bdff9e936620346fad659fb91759 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 59ac50bbc1abc37b34bc4cd47b564d770de2828e045ba59c056f873543de10b3 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java f2a87c88ab23fa4601a985eb69bdc8b4f81cabfab04fdc3544ecefde207e08d4 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ae1d6706f723517e03a04ab578a539fa3df66fe38adad113f10b61eabc524d09 -F ext/jni/src/tests/000_first.test 954c19705c791023eb5a473de0851d3727406fdef25f4b2521b88972280b4111 +F ext/jni/src/tests/000_first.test 461f465cd1e9c60f19a8fd4231721c1bbd01702d9677696d56087a58f9d2e09e F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ab9c945bb0b4210b3f47e6341f150f8a7cc45f9e4e4c2247e91d2528ed4772a6 -R 71db74e5804fc1b921164f2085cce4db +P 5323e4fd254274cc527af7536c622b786394599c68eca2da6c7fc641727dbdb2 +R c0ec1fc5e7c5611a47b223974ef77855 U stephan -Z d1b5b5a17f205dc2910f24396b36f01b +Z 3ccc1c720ec01884cd7c12ccaa2f6b70 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7c80c48420..44b47e7b2a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -5323e4fd254274cc527af7536c622b786394599c68eca2da6c7fc641727dbdb2 \ No newline at end of file +8c5b6d893df4a4e82c6d8e07507fc160b11412ede4bb903ff4e3f5ffa59a9cb9 \ No newline at end of file From 5158a9a9a736750a80cc2c53c4b68c99e05b966e Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 13:51:50 +0000 Subject: [PATCH 102/148] Add --json and --json-block SQLTester commands. FossilOrigin-Name: 478129d901824e675d86494044f73c313532e9f80e7ee6f425474df8237a82f5 --- .../src/org/sqlite/jni/tester/SQLTester.java | 96 ++++++++++++++----- ext/jni/src/tests/000_first.test | 32 ++++--- manifest | 14 +-- manifest.uuid | 2 +- 4 files changed, 101 insertions(+), 43 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index e4d21f88d0..cd13206116 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -521,7 +521,6 @@ class GlobCommand extends Command { public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); - t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); @@ -539,6 +538,16 @@ class GlobCommand extends Command { } } +//! --json command +class JsonCommand extends ResultCommand { + public JsonCommand(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + public JsonBlockCommand(){ super(true); } +} + //! --new command class NewDbCommand extends Command { public void process(SQLTester t, String[] argv, String content) throws Exception{ @@ -551,7 +560,7 @@ class NewDbCommand extends Command { } } -//! Placeholder dummy/no-op command +//! Placeholder dummy/no-op commands class NoopCommand extends Command { public void process(SQLTester t, String[] argv, String content) throws Exception{ } @@ -596,15 +605,18 @@ class PrintCommand extends Command { } } +//! --result command class ResultCommand extends Command { + private final ResultBufferMode bufferMode; + protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand(){ this(ResultBufferMode.ESCAPED); } public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,-1); affirmNoContent(content); t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, - ResultRowMode.ONELINE, sql); + int rc = t.execSql(null, true, bufferMode, ResultRowMode.ONELINE, sql); final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); @@ -614,6 +626,7 @@ class ResultCommand extends Command { } } +//! --run command class RunCommand extends Command { public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0,1); @@ -630,10 +643,15 @@ class RunCommand extends Command { } } +//! --tableresult command class TableResultCommand extends Command { + private final boolean jsonMode; + protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand(){ this(false); } public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,0); affirmHasContent(content); + t.incrementTestCounter(); if( !content.endsWith("\n--end") ){ Util.toss(TestFailure.class, argv[0], " must be terminated with --end."); }else{ @@ -642,7 +660,8 @@ class TableResultCommand extends Command { } final String[] globs = content.split("\s*\n\s*"); if( globs.length < 1 ){ - Util.toss(TestFailure.class, argv[0], " requires 1 or more globs."); + Util.toss(TestFailure.class, argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); } final String sql = t.takeInputBuffer(); t.execSql(null, true, ResultBufferMode.ESCAPED, ResultRowMode.NEWLINE, sql); @@ -654,14 +673,21 @@ class TableResultCommand extends Command { } for(int i = 0; i < res.length; ++i){ final String glob = GlobCommand.globToStrglob(globs[i]).replaceAll("\s+"," "); - if( 0 != sqlite3_strglob(glob, res[i]) ){ - Util.toss(TestFailure.class, argv[0], " glob {",glob, - "} does not match: {",res[i],"}"); + //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + Util.toss(TestFailure.class, argv[0], " json <<",glob, + ">> does not match: <<",res[i],">>"); + } + }else if( 0 != sqlite3_strglob(glob, res[i]) ){ + Util.toss(TestFailure.class, argv[0], " glob <<",glob, + ">> does not match: <<",res[i],">>"); } } } } +//! --testcase command class TestCaseCommand extends Command { public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); @@ -673,37 +699,48 @@ class TestCaseCommand extends Command { } } +/** + Helper for dispatching Command instances. +*/ class CommandDispatcher { private static java.util.Map commandMap = new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ static Command getCommandByName(String name){ - // TODO? Do this dispatching using a custom annotation on - // Command impls. That requires a surprisingly huge amount - // of code, though. Command rv = commandMap.get(name); if( null!=rv ) return rv; switch(name){ - case "close": rv = new CloseDbCommand(); break; - case "db": rv = new DbCommand(); break; - case "glob": rv = new GlobCommand(); break; - case "new": rv = new NewDbCommand(); break; - case "notglob": rv = new NotGlobCommand(); break; - case "null": rv = new NullCommand(); break; - case "oom": rv = new NoopCommand(); break; - case "open": rv = new OpenDbCommand(); break; - case "print": rv = new PrintCommand(); break; - case "result": rv = new ResultCommand(); break; - case "run": rv = new RunCommand(); break; + case "close": rv = new CloseDbCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; case "tableresult": rv = new TableResultCommand(); break; - case "testcase": rv = new TestCaseCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; default: rv = null; break; } if( null!=rv ) commandMap.put(name, rv); return rv; } - @SuppressWarnings("unchecked") + /** + Treats argv[0] as a command name, looks it up with + getCommandByName(), and calls process() on that instance, passing + it arguments given to this function. + */ static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{ final Command cmd = getCommandByName(argv[0]); if(null == cmd){ @@ -722,7 +759,12 @@ class CommandDispatcher { } } +/** + General utilities for the SQLTester bits. +*/ final class Util { + + //! Throws a new T, appending all msg args into a string for the message. public static void toss(Class errorType, Object... msg) throws Exception { StringBuilder sb = new StringBuilder(); for(Object s : msg) sb.append(s); @@ -739,6 +781,7 @@ final class Util { toss(IllegalArgumentException.class, msg); } + //! Tries to delete the given file, silently ignoring failure. public static void unlink(String filename){ try{ final java.io.File f = new java.io.File(filename); @@ -748,6 +791,11 @@ final class Util { } } + /** + Appends all entries in argv[1..end] into a space-separated + string, argv[0] is not included because it's expected to + be a command name. + */ public static String argvToString(String[] argv){ StringBuilder sb = new StringBuilder(); for(int i = 1; i < argv.length; ++i ){ diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 742ebfa451..2f970b243d 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -12,8 +12,8 @@ junk --new SQLTester.db --null zilch --run -select 1; -select 2; + SELECT 1; + SELECT 2; -- comment -- uncomment to introduce intentional syntax error --oom @@ -22,24 +22,34 @@ This is from the print command's body. --print Also from the print command. --- also ignored ---testcase first -select 'a', 'b'; -select 'a', 'b'; +--testcase 1 + SELECT 'a', 'b'; + SELECT 'a', 'b'; --result a b a b ---testcase second -select 123 +--testcase 2 + SELECT 123 --glob ### ---testcase third -select 'a' +--testcase 3 + SELECT 'a' --notglob # --close --open SQLTester.db --print Re-opened db. --testcase fourth -SELECT 1, 2; -SELECT 'b', 'c'; + SELECT 1, 2; + SELECT 'b', 'c'; --tableresult [0-9] # b c --end +--testcase json-array-1 +SELECT json_array(1,2,3) +--json [1,2,3] +--testcase json-array-2 + SELECT json_array(1,2,3); + SELECT json_object('a',1,'b',2); +--json-block +[1,2,3] +{"a":1,"b":2} +--end --an-uknown-command diff --git a/manifest b/manifest index 7f72a5fc63..aacd223c2e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sSQLTester\s--tableresult\scommand. -D 2023-08-09T13:16:10.208 +C Add\s--json\sand\s--json-block\sSQLTester\scommands. +D 2023-08-09T13:51:50.893 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 59ac50bbc1abc37b34bc4cd47b564d770de2828e045ba59c056f873543de10b3 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 41f48e3d4834e08bf35e21f8c62b8350c61161aafb498e4961c642194c06a29d F ext/jni/src/org/sqlite/jni/tester/TestScript.java f2a87c88ab23fa4601a985eb69bdc8b4f81cabfab04fdc3544ecefde207e08d4 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ae1d6706f723517e03a04ab578a539fa3df66fe38adad113f10b61eabc524d09 -F ext/jni/src/tests/000_first.test 461f465cd1e9c60f19a8fd4231721c1bbd01702d9677696d56087a58f9d2e09e +F ext/jni/src/tests/000_first.test ea6ac62e13a3787f3f6d402fa5e9c7a7e85b43ec11ce10e260d673be7ecc537b F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5323e4fd254274cc527af7536c622b786394599c68eca2da6c7fc641727dbdb2 -R c0ec1fc5e7c5611a47b223974ef77855 +P 8c5b6d893df4a4e82c6d8e07507fc160b11412ede4bb903ff4e3f5ffa59a9cb9 +R 90d3df2a48743fa4d0421d9f7ba4a451 U stephan -Z 3ccc1c720ec01884cd7c12ccaa2f6b70 +Z 0acfbb2c69aeffb3bdb56736e07c0e08 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 44b47e7b2a..78d0e64d71 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8c5b6d893df4a4e82c6d8e07507fc160b11412ede4bb903ff4e3f5ffa59a9cb9 \ No newline at end of file +478129d901824e675d86494044f73c313532e9f80e7ee6f425474df8237a82f5 \ No newline at end of file From 4340f27e179c84531fb32b16d29d3aa5cb18488d Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 14:24:22 +0000 Subject: [PATCH 103/148] Implement the SQLTester's result escaping rules. FossilOrigin-Name: 61bb950873a1ec45a71b15a0ab5128a50417c4ecdd7d5bd9add0c18afcbadf34 --- ext/jni/src/c/sqlite3-jni.h | 2 +- .../src/org/sqlite/jni/tester/SQLTester.java | 51 +++++++++++++++---- .../jni/tester/test-script-interpreter.md | 2 +- ext/jni/src/tests/000_first.test | 8 +-- manifest | 18 +++---- manifest.uuid | 2 +- 6 files changed, 59 insertions(+), 24 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index e2cce883b3..9a6675f702 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1120,7 +1120,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_00024Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2 (JNIEnv *, jclass, jobject, jint, jobject); /* diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index cd13206116..0e70ad6f8a 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.ArrayList; import java.util.Arrays; import java.nio.charset.StandardCharsets; +import java.util.regex.*; import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; @@ -263,9 +264,38 @@ public class SQLTester { void incrementTestCounter(){ ++nTest; ++nTotalTest; } + static final Pattern patternPlain = Pattern.compile("[\\W]", Pattern.MULTILINE); + static final Pattern patternSquiggly = Pattern.compile("[{}]", Pattern.MULTILINE); + + /** + Returns v or some escaped form of v, as defined in the tester's + spec doc. + */ String escapeSqlValue(String v){ - // TODO: implement the escaping rules - return v; + Matcher m = patternPlain.matcher(v); + if( !m.find() ){ + return v /* no escaping needed */; + } + m = patternSquiggly.matcher(v); + if( !m.find() ){ + return "{"+v+"}"; + } + final StringBuilder sb = new StringBuilder("\""); + final int n = v.length(); + for(int i = 0; i < n; ++i){ + final char ch = v.charAt(i); + switch(ch){ + case '\\': sb.append("\\\\"); break; + case '"': sb.append("\\\""); break; + default: + //verbose("CHAR ",(int)ch," ",ch," octal=",String.format("\\%03o", (int)ch)); + if( (int)ch < 32 ) sb.append(String.format("\\%03o", (int)ch)); + else sb.append(ch); + break; + } + } + sb.append("\""); + return sb.toString(); } private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ @@ -327,12 +357,12 @@ public class SQLTester { continue; } switch(appendMode){ - case ESCAPED: - sb.append( escapeSqlValue(val) ); - break; case ASIS: sb.append( val ); break; + case ESCAPED: + sb.append( escapeSqlValue(val) ); + break; default: Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); } @@ -619,7 +649,7 @@ class ResultCommand extends Command { int rc = t.execSql(null, true, bufferMode, ResultRowMode.ONELINE, sql); final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + t.verbose(argv[0]," result buffer:\n", result,"\nargs:\n",sArgs); if( !result.equals(sArgs) ){ Util.toss(TestFailure.class, argv[0]," comparison failed."); } @@ -658,13 +688,15 @@ class TableResultCommand extends Command { int n = content.length(); content = content.substring(0, n-6); } - final String[] globs = content.split("\s*\n\s*"); + final String[] globs = content.split("\\s*\\n\\s*"); if( globs.length < 1 ){ Util.toss(TestFailure.class, argv[0], " requires 1 or more ", (jsonMode ? "json snippets" : "globs"),"."); } final String sql = t.takeInputBuffer(); - t.execSql(null, true, ResultBufferMode.ESCAPED, ResultRowMode.NEWLINE, sql); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); final String rbuf = t.getResultText(); final String[] res = rbuf.split("\n"); if( res.length != globs.length ){ @@ -672,7 +704,8 @@ class TableResultCommand extends Command { res.length," row(s) but expecting ",globs.length); } for(int i = 0; i < res.length; ++i){ - final String glob = GlobCommand.globToStrglob(globs[i]).replaceAll("\s+"," "); + final String glob = GlobCommand.globToStrglob(globs[i]) + .replaceAll("\\s+"," "); //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); if( jsonMode ){ if( !glob.equals(res[i]) ){ diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 4f0052fc11..5e89464664 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -241,7 +241,7 @@ by that argument. The --json and --json-block commands work like --result and --tableresult, respectively. The difference is that column values are appended to the -result buffer literally, without every enclosing the values in `{...}` or +result buffer literally, without ever enclosing the values in `{...}` or `"..."` and without escaping any characters in the column value and comparison is always an exact strcmp() not a GLOB. diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 2f970b243d..2cdec0dd85 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -23,9 +23,11 @@ This is from the print command's body. Also from the print command. --- also ignored --testcase 1 - SELECT 'a', 'b'; - SELECT 'a', 'b'; ---result a b a b + SELECT 'a b', 'c'; + SELECT 'd', 'e'; + SELECT '{}', 'f'; + SELECT '{ }', 'g' +--result {a b} c d e "{}" f "{\011}" g --testcase 2 SELECT 123 --glob ### diff --git a/manifest b/manifest index aacd223c2e..9d5298a147 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\s--json\sand\s--json-block\sSQLTester\scommands. -D 2023-08-09T13:51:50.893 +C Implement\sthe\sSQLTester's\sresult\sescaping\srules. +D 2023-08-09T14:24:22.127 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,7 +233,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c 07871efe50bb090023259c95df8b851c617917f762b60a3460c9778b2ae6356b -F ext/jni/src/c/sqlite3-jni.h ec38592e88d32f09ba4bde13f2e135bb7cf8712356b807df521b3fc99edeab32 +F ext/jni/src/c/sqlite3-jni.h 103ecb1e9213e904f7ba7f955fe305587f5f0cd55290636832f639b81270b5f6 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 41f48e3d4834e08bf35e21f8c62b8350c61161aafb498e4961c642194c06a29d +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f21b90075f70d0f2550847424fc4cfc7ab80ef930b97a02f4e032150f3a7e8a7 F ext/jni/src/org/sqlite/jni/tester/TestScript.java f2a87c88ab23fa4601a985eb69bdc8b4f81cabfab04fdc3544ecefde207e08d4 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ae1d6706f723517e03a04ab578a539fa3df66fe38adad113f10b61eabc524d09 -F ext/jni/src/tests/000_first.test ea6ac62e13a3787f3f6d402fa5e9c7a7e85b43ec11ce10e260d673be7ecc537b +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 480825a33a4d8df19aac8d6012b1041e0f679198a0ce9fbf189363e8167b51b1 +F ext/jni/src/tests/000_first.test c9132e01fbb050af57db2f8018f99e7084b61557afd56107eeb830e84f8ad852 F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 8c5b6d893df4a4e82c6d8e07507fc160b11412ede4bb903ff4e3f5ffa59a9cb9 -R 90d3df2a48743fa4d0421d9f7ba4a451 +P 478129d901824e675d86494044f73c313532e9f80e7ee6f425474df8237a82f5 +R 03dd2e2f7f4ef00e6ca61e5373fcfe05 U stephan -Z 0acfbb2c69aeffb3bdb56736e07c0e08 +Z 5d907161d89ca6745e892ceb9fdce9a9 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 78d0e64d71..ec71c46976 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -478129d901824e675d86494044f73c313532e9f80e7ee6f425474df8237a82f5 \ No newline at end of file +61bb950873a1ec45a71b15a0ab5128a50417c4ecdd7d5bd9add0c18afcbadf34 \ No newline at end of file From aa6b35cc8068d7473e632542bb271373030d2ce5 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 14:43:54 +0000 Subject: [PATCH 104/148] Extend SQLTester glob support with '#'. FossilOrigin-Name: 756ef83f45b69d9f78965ef1171d36477a32f938fe179e59b95f32f07849c0e5 --- ext/jni/src/c/sqlite3-jni.c | 123 +++++++++++++++++- ext/jni/src/c/sqlite3-jni.h | 8 ++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 8 ++ .../src/org/sqlite/jni/tester/SQLTester.java | 37 ++++-- ext/jni/src/tests/000_first.test | 11 +- manifest | 20 +-- manifest.uuid | 2 +- 7 files changed, 182 insertions(+), 27 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 99eb167510..4ecd9a980e 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -3053,11 +3053,11 @@ static int s3jni_strlike_glob(int isLike, JNIEnv *const env, return rc; } -JDECL(int,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ +JDECL(jint,1strglob)(JENV_CSELF, jbyteArray baG, jbyteArray baT){ return s3jni_strlike_glob(0, env, baG, baT, 0); } -JDECL(int,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ +JDECL(jint,1strlike)(JENV_CSELF, jbyteArray baG, jbyteArray baT, jint escChar){ return s3jni_strlike_glob(1, env, baG, baT, escChar); } @@ -4045,6 +4045,125 @@ static void SQLTester_dup_count_func( p->nDup = 0; } +/* +** Return non-zero if string z matches glob pattern zGlob and zero if the +** pattern does not match. +** +** To repeat: +** +** zero == no match +** non-zero == match +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** '#' Matches any sequence of one or more digits with an +** optional + or - sign in front, or a hexadecimal +** literal of the form 0x... +*/ +static int SQLTester_strnotglob(const char *zGlob, const char *z){ + int c, c2; + int invert; + int seen; + + while( (c = (*(zGlob++)))!=0 ){ + if( c=='*' ){ + while( (c=(*(zGlob++))) == '*' || c=='?' ){ + if( c=='?' && (*(z++))==0 ) return 0; + } + if( c==0 ){ + return 1; + }else if( c=='[' ){ + while( *z && SQLTester_strnotglob(zGlob-1,z)==0 ){ + z++; + } + return (*z)!=0; + } + while( (c2 = (*(z++)))!=0 ){ + while( c2!=c ){ + c2 = *(z++); + if( c2==0 ) return 0; + } + if( SQLTester_strnotglob(zGlob,z) ) return 1; + } + return 0; + }else if( c=='?' ){ + if( (*(z++))==0 ) return 0; + }else if( c=='[' ){ + int prior_c = 0; + seen = 0; + invert = 0; + c = *(z++); + if( c==0 ) return 0; + c2 = *(zGlob++); + if( c2=='^' ){ + invert = 1; + c2 = *(zGlob++); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = *(zGlob++); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){ + c2 = *(zGlob++); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = *(zGlob++); + } + if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='#' ){ + if( z[0]=='0' + && (z[1]=='x' || z[1]=='X') + && sqlite3Isxdigit(z[2]) + ){ + z += 3; + while( sqlite3Isxdigit(z[0]) ){ z++; } + }else{ + if( (z[0]=='-' || z[0]=='+') && sqlite3Isdigit(z[1]) ) z++; + if( !sqlite3Isdigit(z[0]) ) return 0; + z++; + while( sqlite3Isdigit(z[0]) ){ z++; } + } + }else{ + if( c!=(*(z++)) ) return 0; + } + } + return *z==0; +} + +JNIEXPORT jint JNICALL +Java_org_sqlite_jni_tester_SQLTester_strglob( + JENV_CSELF, jbyteArray baG, jbyteArray baT +){ + int rc = 0; + jbyte * const pG = JBA_TOC(baG); + jbyte * const pT = pG ? JBA_TOC(baT) : 0; + OOM_CHECK(pT); + + /* Note that we're relying on the byte arrays having been + NUL-terminated on the Java side. */ + rc = !SQLTester_strnotglob((const char *)pG, (const char *)pT); + JBA_RELEASE(baG, pG); + JBA_RELEASE(baT, pT); + return rc; +} + + static int SQLTester_auto_extension(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ sqlite3_create_function(pDb, "dup", 1, SQLITE_UTF8, &SQLTester, diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 9a6675f702..5a2da91f32 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1927,6 +1927,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_fts5_1tokenizer_xTokenize #ifdef __cplusplus extern "C" { #endif +/* + * Class: org_sqlite_jni_tester_SQLTester + * Method: strglob + * Signature: ([B[B)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_tester_SQLTester_strglob + (JNIEnv *, jclass, jbyteArray, jbyteArray); + /* * Class: org_sqlite_jni_tester_SQLTester * Method: installCustomExtensions diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 257864e741..6847b05da3 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -850,6 +850,10 @@ public final class SQLite3Jni { public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + /** + Internal impl of the public sqlite3_strglob() method. Neither argument + may be NULL and both _MUST_ be NUL-terminated. + */ private static native int sqlite3_strglob( @NotNull byte[] glob, @NotNull byte[] txt ); @@ -861,6 +865,10 @@ public final class SQLite3Jni { ); } + /** + Internal impl of the public sqlite3_strlike() method. Neither + argument may be NULL and both _MUST_ be NUL-terminated. + */ private static native int sqlite3_strlike( @NotNull byte[] glob, @NotNull byte[] txt, int escChar ); diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 0e70ad6f8a..1d26c6aecd 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -416,6 +416,25 @@ public class SQLTester { } } + /** + Internal impl of the public strglob() method. Neither argument + may be NULL and both _MUST_ be NUL-terminated. + */ + private static native int strglob(byte[] glob, byte[] txt); + + /** + Works essentially the same as sqlite3_strglob() except that the + glob character '#' matches a sequence of one or more digits. It + does not match when it appears at the start or middle of a series + of digits, e.g. "#23" or "1#3", but will match at the end, + e.g. "12#". + */ + public static int strglob(String glob, String txt){ + return strglob( + (glob+"\0").getBytes(StandardCharsets.UTF_8), + (txt+"\0").getBytes(StandardCharsets.UTF_8) + ); + } private static native void installCustomExtensions(); static { @@ -541,13 +560,6 @@ class GlobCommand extends Command { public GlobCommand(){} protected GlobCommand(boolean negate){ this.negate = negate; } - public static String globToStrglob(String g){ - /* FIXME: '#' support needs to match 1+ digits, but - sqlite3_strglob() does not support that option. We'll - need a custom glob routine for that. */; - return g.replace("#","[0-9]").trim(); - } - public void process(SQLTester t, String[] argv, String content) throws Exception{ argcCheck(argv,1); affirmNoContent(content); @@ -559,8 +571,8 @@ class GlobCommand extends Command { final String result = t.getResultText().trim(); final String sArgs = Util.argvToString(argv); //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = globToStrglob(argv[1]); - rc = sqlite3_strglob(glob, result); + final String glob = argv[1]; + rc = SQLTester.strglob(glob, result); if( (negate && 0==rc) || (!negate && 0!=rc) ){ Util.toss(TestFailure.class, argv[0], " mismatch: ", glob," vs input: ",result); @@ -649,7 +661,7 @@ class ResultCommand extends Command { int rc = t.execSql(null, true, bufferMode, ResultRowMode.ONELINE, sql); final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - t.verbose(argv[0]," result buffer:\n", result,"\nargs:\n",sArgs); + //t.verbose(argv[0]," result buffer:\n", result,"\nargs:\n",sArgs); if( !result.equals(sArgs) ){ Util.toss(TestFailure.class, argv[0]," comparison failed."); } @@ -704,15 +716,14 @@ class TableResultCommand extends Command { res.length," row(s) but expecting ",globs.length); } for(int i = 0; i < res.length; ++i){ - final String glob = GlobCommand.globToStrglob(globs[i]) - .replaceAll("\\s+"," "); + final String glob = globs[i].replaceAll("\\s+"," "); //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); if( jsonMode ){ if( !glob.equals(res[i]) ){ Util.toss(TestFailure.class, argv[0], " json <<",glob, ">> does not match: <<",res[i],">>"); } - }else if( 0 != sqlite3_strglob(glob, res[i]) ){ + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ Util.toss(TestFailure.class, argv[0], " glob <<",glob, ">> does not match: <<",res[i],">>"); } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 2cdec0dd85..a09864fa71 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -30,7 +30,7 @@ Also from the print command. --result {a b} c d e "{}" f "{\011}" g --testcase 2 SELECT 123 ---glob ### +--glob 1# --testcase 3 SELECT 'a' --notglob # @@ -54,4 +54,13 @@ SELECT json_array(1,2,3) [1,2,3] {"a":1,"b":2} --end +--testcase table-result-globs + SELECT 123; + SELECT 'aBc'; + SELECT 456; +--tableresult + # + [a-z][A-Z][a-z] + 4# +--end --an-uknown-command diff --git a/manifest b/manifest index 9d5298a147..cc0df7415d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Implement\sthe\sSQLTester's\sresult\sescaping\srules. -D 2023-08-09T14:24:22.127 +C Extend\sSQLTester\sglob\ssupport\swith\s'#'. +D 2023-08-09T14:43:54.326 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -232,8 +232,8 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 07871efe50bb090023259c95df8b851c617917f762b60a3460c9778b2ae6356b -F ext/jni/src/c/sqlite3-jni.h 103ecb1e9213e904f7ba7f955fe305587f5f0cd55290636832f639b81270b5f6 +F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520 +F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -252,7 +252,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 6d868925dd0260805e922b1a598d3e377f87f90e16cae327aa7b7beeecac45a9 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2fc40765b9f45973103d12d6e9d7df1c9d93afaba7b884a0f16a2fde040c374c F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f21b90075f70d0f2550847424fc4cfc7ab80ef930b97a02f4e032150f3a7e8a7 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 07592cf1ba9371a766a289df2f836458522f713e2934f08d06ba3da9dd6ea8c5 F ext/jni/src/org/sqlite/jni/tester/TestScript.java f2a87c88ab23fa4601a985eb69bdc8b4f81cabfab04fdc3544ecefde207e08d4 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 480825a33a4d8df19aac8d6012b1041e0f679198a0ce9fbf189363e8167b51b1 -F ext/jni/src/tests/000_first.test c9132e01fbb050af57db2f8018f99e7084b61557afd56107eeb830e84f8ad852 +F ext/jni/src/tests/000_first.test 2b46743f1dcfcc37c76c3c8a2a6a9fd1b9b6403d5fce39d61a63db0e066617e3 F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 478129d901824e675d86494044f73c313532e9f80e7ee6f425474df8237a82f5 -R 03dd2e2f7f4ef00e6ca61e5373fcfe05 +P 61bb950873a1ec45a71b15a0ab5128a50417c4ecdd7d5bd9add0c18afcbadf34 +R 67f298c35f6658c1dcfa1d3c6d43985a U stephan -Z 5d907161d89ca6745e892ceb9fdce9a9 +Z eb9db15edefb1a30f58396ebdbab4f61 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ec71c46976..58f90939c3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -61bb950873a1ec45a71b15a0ab5128a50417c4ecdd7d5bd9add0c18afcbadf34 \ No newline at end of file +756ef83f45b69d9f78965ef1171d36477a32f938fe179e59b95f32f07849c0e5 \ No newline at end of file From 5b400552be4b47afc4aadc4e06e35b4de8155873 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 14:47:01 +0000 Subject: [PATCH 105/148] Add a test for the --null command. FossilOrigin-Name: 83ac815debcc75dac1fbbdc17736f5e33fb675fdab0bf649367592a0d18074e4 --- ext/jni/src/tests/000_first.test | 7 +++++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index a09864fa71..db3d8e81f5 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -10,7 +10,6 @@ junk # --open nope.db /* must throw */ --new SQLTester.db ---null zilch --run SELECT 1; SELECT 2; @@ -44,6 +43,10 @@ Also from the print command. [0-9] # b c --end +--null zilch +--testcase null-command + SELECT null; +--result zilch --testcase json-array-1 SELECT json_array(1,2,3) --json [1,2,3] @@ -63,4 +66,4 @@ SELECT json_array(1,2,3) [a-z][A-Z][a-z] 4# --end ---an-uknown-command +--an-unknown-command diff --git a/manifest b/manifest index cc0df7415d..1abf04a663 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Extend\sSQLTester\sglob\ssupport\swith\s'#'. -D 2023-08-09T14:43:54.326 +C Add\sa\stest\sfor\sthe\s--null\scommand. +D 2023-08-09T14:47:01.213 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -269,7 +269,7 @@ F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 07592cf1ba9371a766a289df2f836458522f713e2934f08d06ba3da9dd6ea8c5 F ext/jni/src/org/sqlite/jni/tester/TestScript.java f2a87c88ab23fa4601a985eb69bdc8b4f81cabfab04fdc3544ecefde207e08d4 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 480825a33a4d8df19aac8d6012b1041e0f679198a0ce9fbf189363e8167b51b1 -F ext/jni/src/tests/000_first.test 2b46743f1dcfcc37c76c3c8a2a6a9fd1b9b6403d5fce39d61a63db0e066617e3 +F ext/jni/src/tests/000_first.test 93905a390107748775acec1bb67dbc26c74e70e96b15f3723767f814d1453dfe F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 61bb950873a1ec45a71b15a0ab5128a50417c4ecdd7d5bd9add0c18afcbadf34 -R 67f298c35f6658c1dcfa1d3c6d43985a +P 756ef83f45b69d9f78965ef1171d36477a32f938fe179e59b95f32f07849c0e5 +R 868fabec57c4571d02ece8c076384b8b U stephan -Z eb9db15edefb1a30f58396ebdbab4f61 +Z 7dc2fe1c269b136ee892b91b8b4ea8b6 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 58f90939c3..48f5d78db6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -756ef83f45b69d9f78965ef1171d36477a32f938fe179e59b95f32f07849c0e5 \ No newline at end of file +83ac815debcc75dac1fbbdc17736f5e33fb675fdab0bf649367592a0d18074e4 \ No newline at end of file From 65c7226e31d9d120474e2c7bc71accb16a21a881 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 15:46:55 +0000 Subject: [PATCH 106/148] Extend TestScript to be able to report why it should be skipped. Expand the test-skipping rules to account for the current spec doc. Add the {} empty-string case to the spec doc. FossilOrigin-Name: 4fcc8cb0cc2bbc0da71bdb99dacfdec54814af4c0e4c37619bad6a8e5fa62937 --- .../src/org/sqlite/jni/tester/SQLTester.java | 11 +++--- .../src/org/sqlite/jni/tester/TestScript.java | 36 ++++++++++++++----- .../jni/tester/test-script-interpreter.md | 11 +++--- ext/jni/src/tests/000_first.test | 3 -- manifest | 18 +++++----- manifest.uuid | 2 +- 6 files changed, 51 insertions(+), 30 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 1d26c6aecd..de18db491b 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -133,8 +133,8 @@ public class SQLTester { currentScript = ts; outln("----->>>>> ",ts.getModuleName()," [",ts.getName(),"]"); if( ts.isIgnored() ){ - outln("WARNING: skipping [",ts.getModuleName(),"] because it contains ", - "content which requires that it be skipped."); + outln("WARNING: skipping [",ts.getModuleName(),"]: ", + ts.getIgnoredReason()); continue; }else{ try{ @@ -272,6 +272,7 @@ public class SQLTester { spec doc. */ String escapeSqlValue(String v){ + if( "".equals(v) ) return "{}"; Matcher m = patternPlain.matcher(v); if( !m.find() ){ return v /* no escaping needed */; @@ -509,7 +510,8 @@ abstract class Command { //! Throws if content is not null. protected void affirmNoContent(String content) throws Exception{ if(null != content){ - Util.badArg(this.getClass().getName()," does not accept content."); + Util.badArg(this.getClass().getName()," does not accept content ", + "but got:\n",content); } } @@ -661,8 +663,9 @@ class ResultCommand extends Command { int rc = t.execSql(null, true, bufferMode, ResultRowMode.ONELINE, sql); final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - //t.verbose(argv[0]," result buffer:\n", result,"\nargs:\n",sArgs); if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nargs:\n",sArgs); Util.toss(TestFailure.class, argv[0]," comparison failed."); } } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 414ea29bc1..ce0fd9e237 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -27,7 +27,7 @@ class TestScript { private String moduleName = null; private List chunks = null; private final Outer outer = new Outer(); - private boolean ignored = false; + private String ignoreReason = null; /* One "chunk" of input, representing a single command and its optional body content. */ @@ -75,7 +75,11 @@ class TestScript { } public boolean isIgnored(){ - return ignored; + return null!=ignoreReason; + } + + public String getIgnoredReason(){ + return ignoreReason; } public void setVerbose(boolean b){ @@ -88,13 +92,30 @@ class TestScript { return this; } + private static final Pattern patternHashLine = + Pattern.compile("^#", Pattern.MULTILINE); /** Returns true if the given script content should be ignored (because it contains certain content which indicates such). */ - public boolean shouldBeIgnored(String content){ - return (null == moduleName) - || content.indexOf("\n|")>=0; + private boolean shouldBeIgnored(String content){ + if( null == moduleName ){ + ignoreReason = "No module name."; + return true; + }else if( content.indexOf("\n|")>=0 ){ + ignoreReason = "Contains newline-pipe combination."; + return true; + }else if( content.indexOf(" MODULE_NAME:")>=0 || + content.indexOf("MIXED_MODULE_NAME:")>=0 ){ + ignoreReason = "Incompatible module script."; + return true; + } + Matcher m = patternHashLine.matcher(content); + if( m.find() ){ + ignoreReason = "C-preprocessor line found."; + return true; + } + return false; } private boolean findModuleName(String content){ @@ -125,8 +146,7 @@ class TestScript { */ private List chunkContent(String content){ findModuleName(content); - ignored = shouldBeIgnored(content); - if( ignored ){ + if( shouldBeIgnored(content) ){ chunks = null; return null; } @@ -134,12 +154,10 @@ class TestScript { // First, strip out any content which we know we can ignore... final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; final String s3Dash = "^---+[^\\n]*\\n"; - final String sTclComment = "^#[^\\n]*\\n"; final String sEmptyLine = "^\\n"; final List lPats = new ArrayList<>(); lPats.add(sCComment); lPats.add(s3Dash); - lPats.add(sTclComment); lPats.add(sEmptyLine); //verbose("Content:").verbose(content).verbose(""); for( String s : lPats ){ diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 5e89464664..cb0047d5b8 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -118,13 +118,16 @@ appended to the result buffer according to the following rules: (In this way, all column values and all row values are separated from each other by a single space.) - * If the sqlite3_column_text() returns NULL, then append "nil" - or + * If sqlite3_column_text() returns NULL, then append "nil" - or some other text that is specified by the --null command - and skip all subsequent rules. - * If sqlite3_column_text() does not contain any special characters, - append it to the result buffer without any formatting and skip all - subsequent rules. + * If sqlite3_column_text() is an empty string, append `{}` to the + result buffer and skip all subsequent rules. + + * If sqlite3_column_text() does not contain any special characters + (non-word characters), append it to the result buffer without + any formatting and skip all subsequent rules. * If sqlite3_column_text() does not contains curly braces, then put the text inside of `{...}` and append it and skip all subsequent rules. diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index db3d8e81f5..4998a9e3d7 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -4,11 +4,8 @@ ** */ -# this line is ignored - junk -# --open nope.db /* must throw */ --new SQLTester.db --run SELECT 1; diff --git a/manifest b/manifest index 1abf04a663..efe964956e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\stest\sfor\sthe\s--null\scommand. -D 2023-08-09T14:47:01.213 +C Extend\sTestScript\sto\sbe\sable\sto\sreport\swhy\sit\sshould\sbe\sskipped.\sExpand\sthe\stest-skipping\srules\sto\saccount\sfor\sthe\scurrent\sspec\sdoc.\sAdd\sthe\s{}\sempty-string\scase\sto\sthe\sspec\sdoc. +D 2023-08-09T15:46:55.578 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 07592cf1ba9371a766a289df2f836458522f713e2934f08d06ba3da9dd6ea8c5 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java f2a87c88ab23fa4601a985eb69bdc8b4f81cabfab04fdc3544ecefde207e08d4 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 480825a33a4d8df19aac8d6012b1041e0f679198a0ce9fbf189363e8167b51b1 -F ext/jni/src/tests/000_first.test 93905a390107748775acec1bb67dbc26c74e70e96b15f3723767f814d1453dfe +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 0889e3155178081ac1da419ab691d7c062ef0f36b7ee1b4560e02e7c0cbab897 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 3e284ba0ca456dd28e79f9283affbff870c0c58489c720e3cf9816299ab849cc +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 7b461e9d0de3466273f6214bb5e97b9261b26277be83d26beea54f48ac9edcf0 +F ext/jni/src/tests/000_first.test 01cb22b7cad4bef6406ba61c230c6a120c2d800364fadefc503a4e313a6fbe97 F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 756ef83f45b69d9f78965ef1171d36477a32f938fe179e59b95f32f07849c0e5 -R 868fabec57c4571d02ece8c076384b8b +P 83ac815debcc75dac1fbbdc17736f5e33fb675fdab0bf649367592a0d18074e4 +R c4ccbbfa4d39592a7573f9290d04e09f U stephan -Z 7dc2fe1c269b136ee892b91b8b4ea8b6 +Z 30c0b98d0025fb4a06bfd82063ce05fe # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 48f5d78db6..67e6e5b317 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -83ac815debcc75dac1fbbdc17736f5e33fb675fdab0bf649367592a0d18074e4 \ No newline at end of file +4fcc8cb0cc2bbc0da71bdb99dacfdec54814af4c0e4c37619bad6a8e5fa62937 \ No newline at end of file From bd87174bae4f2521f52cccf4b55d35c169c4996a Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 16:03:12 +0000 Subject: [PATCH 107/148] Update the definition of "special characters" for the SQLTester and correct the code to match it. FossilOrigin-Name: 217f6e0c9c09c576b09ea59fce085a53d1a133927046102b4d00fd58109efc93 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 6 ++++-- .../sqlite/jni/tester/test-script-interpreter.md | 8 +++++--- ext/jni/src/tests/000_first.test | 5 +++-- manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index de18db491b..67e28dbc21 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -264,7 +264,9 @@ public class SQLTester { void incrementTestCounter(){ ++nTest; ++nTotalTest; } - static final Pattern patternPlain = Pattern.compile("[\\W]", Pattern.MULTILINE); + static final Pattern patternSpecial = Pattern.compile( + "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]", Pattern.MULTILINE + ); static final Pattern patternSquiggly = Pattern.compile("[{}]", Pattern.MULTILINE); /** @@ -273,7 +275,7 @@ public class SQLTester { */ String escapeSqlValue(String v){ if( "".equals(v) ) return "{}"; - Matcher m = patternPlain.matcher(v); + Matcher m = patternSpecial.matcher(v); if( !m.find() ){ return v /* no escaping needed */; } diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index cb0047d5b8..d87ce4494a 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -125,9 +125,11 @@ appended to the result buffer according to the following rules: * If sqlite3_column_text() is an empty string, append `{}` to the result buffer and skip all subsequent rules. - * If sqlite3_column_text() does not contain any special characters - (non-word characters), append it to the result buffer without - any formatting and skip all subsequent rules. + * If sqlite3_column_text() does not contain any special + characters, append it to the result buffer without any + formatting and skip all subsequent rules. Special characters are: + 0x00 to 0x20 (inclusive), double-quote (0x22), backslash (0x5c), + curly braces (0x7b and 0x7d). * If sqlite3_column_text() does not contains curly braces, then put the text inside of `{...}` and append it and skip all subsequent rules. diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 4998a9e3d7..0382c14ec0 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -22,8 +22,9 @@ Also from the print command. SELECT 'a b', 'c'; SELECT 'd', 'e'; SELECT '{}', 'f'; - SELECT '{ }', 'g' ---result {a b} c d e "{}" f "{\011}" g + SELECT '{ }', 'g'; + SELECT '(a-b-c)', '[a-b-c]'; +--result {a b} c d e "{}" f "{\011}" g (a-b-c) [a-b-c] --testcase 2 SELECT 123 --glob 1# diff --git a/manifest b/manifest index efe964956e..116f24efda 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Extend\sTestScript\sto\sbe\sable\sto\sreport\swhy\sit\sshould\sbe\sskipped.\sExpand\sthe\stest-skipping\srules\sto\saccount\sfor\sthe\scurrent\sspec\sdoc.\sAdd\sthe\s{}\sempty-string\scase\sto\sthe\sspec\sdoc. -D 2023-08-09T15:46:55.578 +C Update\sthe\sdefinition\sof\s"special\scharacters"\sfor\sthe\sSQLTester\sand\scorrect\sthe\scode\sto\smatch\sit. +D 2023-08-09T16:03:12.027 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 0889e3155178081ac1da419ab691d7c062ef0f36b7ee1b4560e02e7c0cbab897 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 7217172ff32f280c1bf62b174b57ffcae5c75552b643a784003c9f9719162656 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 3e284ba0ca456dd28e79f9283affbff870c0c58489c720e3cf9816299ab849cc -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 7b461e9d0de3466273f6214bb5e97b9261b26277be83d26beea54f48ac9edcf0 -F ext/jni/src/tests/000_first.test 01cb22b7cad4bef6406ba61c230c6a120c2d800364fadefc503a4e313a6fbe97 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 44449866970341076cec355193f89646825270a2f4f4073b55150cc4a55100d4 +F ext/jni/src/tests/000_first.test 66dd69f5412b87b063982d1aad6907fbe472bc627ca863ae33145e4f54499b8a F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 83ac815debcc75dac1fbbdc17736f5e33fb675fdab0bf649367592a0d18074e4 -R c4ccbbfa4d39592a7573f9290d04e09f +P 4fcc8cb0cc2bbc0da71bdb99dacfdec54814af4c0e4c37619bad6a8e5fa62937 +R 50e013996507ced6d098ec0b617f8f6d U stephan -Z 30c0b98d0025fb4a06bfd82063ce05fe +Z d066411b7d335d12f1361c829dcf4913 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 67e6e5b317..7d5f2d3fc0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4fcc8cb0cc2bbc0da71bdb99dacfdec54814af4c0e4c37619bad6a8e5fa62937 \ No newline at end of file +217f6e0c9c09c576b09ea59fce085a53d1a133927046102b4d00fd58109efc93 \ No newline at end of file From 589857b96587797b33f673f81bce5698da731661 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 16:29:07 +0000 Subject: [PATCH 108/148] Strip --oom commands from SQLTester input since (A) we can't currently do anything with them and (B) they can appear as body content of --testcase commands and the current parser cannot deal with that. If --verbose is provided once, emit the name and args of each command as it's run. If --verbose is used twice or more, also emit the command's body text, if any. FossilOrigin-Name: 0770e8467d4bb9490d9ed6e8a20766ffee7049cc3667db6d036c13fccbb6f3ab --- .../src/org/sqlite/jni/tester/SQLTester.java | 20 ++++++++++-------- .../src/org/sqlite/jni/tester/TestScript.java | 21 ++++++++++++++++--- manifest | 14 ++++++------- manifest.uuid | 2 +- 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 67e28dbc21..87f59f7632 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -80,21 +80,26 @@ public class SQLTester { private int iCurrentDb = 0; private final String initialDbName = "test.db"; private TestScript currentScript; + private int verbosity = 0; public SQLTester(){ reset(); } - public void setVerbose(boolean b){ - this.outer.setVerbose(b); + public void setVerbose(int level){ + verbosity = level; + this.outer.setVerbose( level!=0 ); + } + public int getVerbosity(){ + return verbosity; } public boolean isVerbose(){ - return this.outer.isVerbose(); + return verbosity>0; } @SuppressWarnings("unchecked") public void verbose(Object... vals){ - outer.verbose(vals); + if( verbosity > 0 ) outer.verbose(vals); } @SuppressWarnings("unchecked") @@ -110,7 +115,7 @@ public class SQLTester { //! Adds the given test script to the to-test list. public void addTestScript(String filename){ listInFiles.add(filename); - verbose("Added file ",filename); + //verbose("Added file ",filename); } public void setupInitialDb() throws Exception { @@ -401,10 +406,7 @@ public class SQLTester { if(a.startsWith("-")){ final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ - t.setVerbose(true); - t.outln("Verbose mode is on."); - }else if( flag.equals("quiet") ) { - t.setVerbose(false); + ++t.verbosity; }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index ce0fd9e237..06e2b0f6df 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -155,10 +155,17 @@ class TestScript { final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; final String s3Dash = "^---+[^\\n]*\\n"; final String sEmptyLine = "^\\n"; + final String sOom = "^--oom\\n" + /* Workaround: --oom is a top-level command in some contexts + and appears in --testcase blocks in others. We don't + do anything with --oom commands aside from ignore them, so + elide them all to fix the --testcase blocks which contain + them. */; final List lPats = new ArrayList<>(); lPats.add(sCComment); lPats.add(s3Dash); lPats.add(sEmptyLine); + lPats.add(sOom); //verbose("Content:").verbose(content).verbose(""); for( String s : lPats ){ final Pattern p = Pattern.compile( @@ -224,13 +231,21 @@ class TestScript { Runs this test script in the context of the given tester object. */ public void run(SQLTester tester) throws Exception { - this.setVerbose(tester.isVerbose()); + final int verbosity = tester.getVerbosity(); + this.setVerbose(verbosity>0); if( null==chunks ){ outer.outln("This test contains content which forces it to be skipped."); }else{ - //int n = 0; + int n = 0; for(CommandChunk chunk : chunks){ - //outer.verbose("CHUNK #",++n," ",chunk,""); + if(verbosity>0){ + outer.out("VERBOSE ",moduleName," #",++n," ",chunk.argv[0], + " ",Util.argvToString(chunk.argv)); + if(verbosity>1 && null!=chunk.content){ + outer.out("\nwith content: ", chunk.content); + } + outer.out("\n"); + } CommandDispatcher.dispatch(tester, chunk.argv, chunk.content); } } diff --git a/manifest b/manifest index 116f24efda..0d08b578df 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Update\sthe\sdefinition\sof\s"special\scharacters"\sfor\sthe\sSQLTester\sand\scorrect\sthe\scode\sto\smatch\sit. -D 2023-08-09T16:03:12.027 +C Strip\s--oom\scommands\sfrom\sSQLTester\sinput\ssince\s(A)\swe\scan't\scurrently\sdo\sanything\swith\sthem\sand\s(B)\sthey\scan\sappear\sas\sbody\scontent\sof\s--testcase\scommands\sand\sthe\scurrent\sparser\scannot\sdeal\swith\sthat.\sIf\s--verbose\sis\sprovided\sonce,\semit\sthe\sname\sand\sargs\sof\seach\scommand\sas\sit's\srun.\sIf\s--verbose\sis\sused\stwice\sor\smore,\salso\semit\sthe\scommand's\sbody\stext,\sif\sany. +D 2023-08-09T16:29:07.987 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,8 +266,8 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 7217172ff32f280c1bf62b174b57ffcae5c75552b643a784003c9f9719162656 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 3e284ba0ca456dd28e79f9283affbff870c0c58489c720e3cf9816299ab849cc +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java fbaaaf3f04451a66d8031a6792945fa7396be0e522774f364c2cf1520b38003a +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 91ee32908b837a561cd2820fa49476743823a7b5e13e43ea8fd577357900fd16 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 44449866970341076cec355193f89646825270a2f4f4073b55150cc4a55100d4 F ext/jni/src/tests/000_first.test 66dd69f5412b87b063982d1aad6907fbe472bc627ca863ae33145e4f54499b8a F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4fcc8cb0cc2bbc0da71bdb99dacfdec54814af4c0e4c37619bad6a8e5fa62937 -R 50e013996507ced6d098ec0b617f8f6d +P 217f6e0c9c09c576b09ea59fce085a53d1a133927046102b4d00fd58109efc93 +R cabb506465cd0b0ec946cd352f846b65 U stephan -Z d066411b7d335d12f1361c829dcf4913 +Z 928111b17e4d371ea24b2ea94c9fa691 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7d5f2d3fc0..d076cf8dbe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -217f6e0c9c09c576b09ea59fce085a53d1a133927046102b4d00fd58109efc93 \ No newline at end of file +0770e8467d4bb9490d9ed6e8a20766ffee7049cc3667db6d036c13fccbb6f3ab \ No newline at end of file From 583fc5b963c3803512d646976c5461d56cd845b8 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 16:56:42 +0000 Subject: [PATCH 109/148] Tweak the SQLTester --verbose and double-verbose output a bit for legibility. FossilOrigin-Name: 46b79afaafda40cb1f920cc96600adf11e8c688184c9559a08eb86776ccf3663 --- ext/jni/src/org/sqlite/jni/tester/Outer.java | 16 ++++++++++------ .../src/org/sqlite/jni/tester/SQLTester.java | 18 ++++++++---------- .../src/org/sqlite/jni/tester/TestScript.java | 15 +++++++++------ manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 36 insertions(+), 31 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java index e4ea783490..e6f90ddf31 100644 --- a/ext/jni/src/org/sqlite/jni/tester/Outer.java +++ b/ext/jni/src/org/sqlite/jni/tester/Outer.java @@ -17,7 +17,7 @@ package org.sqlite.jni.tester; Console output utility class. */ class Outer { - public boolean verbose = false; + public int verbosity = 0; public static void out(Object val){ System.out.print(val); @@ -40,17 +40,21 @@ class Outer { @SuppressWarnings("unchecked") public Outer verbose(Object... vals){ - if(verbose){ - out("VERBOSE: "); + if(verbosity>0){ + out("VERBOSE",(verbosity>1 ? "+: " : ": ")); outln(vals); } return this; } - public void setVerbose(boolean b){ - verbose = b; + public void setVerbosity(int level){ + verbosity = level; } - public boolean isVerbose(){return verbose;} + public int getVerbosity(){ + return verbosity; + } + + public boolean isVerbose(){return verbosity > 0;} } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 87f59f7632..c6805cf540 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -80,26 +80,24 @@ public class SQLTester { private int iCurrentDb = 0; private final String initialDbName = "test.db"; private TestScript currentScript; - private int verbosity = 0; public SQLTester(){ reset(); } - public void setVerbose(int level){ - verbosity = level; - this.outer.setVerbose( level!=0 ); + public void setVerbosity(int level){ + this.outer.setVerbosity( level ); } public int getVerbosity(){ - return verbosity; + return this.outer.getVerbosity(); } public boolean isVerbose(){ - return verbosity>0; + return this.outer.isVerbose(); } @SuppressWarnings("unchecked") public void verbose(Object... vals){ - if( verbosity > 0 ) outer.verbose(vals); + outer.verbose(vals); } @SuppressWarnings("unchecked") @@ -406,7 +404,7 @@ public class SQLTester { if(a.startsWith("-")){ final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ - ++t.verbosity; + t.setVerbosity(t.getVerbosity() + 1); }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } @@ -546,7 +544,7 @@ class CloseDbCommand extends Command { id = t.getCurrentDbId(); } t.closeDb(id); - //t.verbose(argv[0]," db ",id); + t.verbose(argv[0]," db ",id); } } @@ -723,7 +721,7 @@ class TableResultCommand extends Command { res.length," row(s) but expecting ",globs.length); } for(int i = 0; i < res.length; ++i){ - final String glob = globs[i].replaceAll("\\s+"," "); + final String glob = globs[i].replaceAll("\\s+"," ").trim(); //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); if( jsonMode ){ if( !glob.equals(res[i]) ){ diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 06e2b0f6df..45195d3314 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -82,8 +82,8 @@ class TestScript { return ignoreReason; } - public void setVerbose(boolean b){ - outer.setVerbose(b); + public void setVerbosity(int level){ + outer.setVerbosity(level); } @SuppressWarnings("unchecked") @@ -220,7 +220,10 @@ class TestScript { final String[] parts = block.split("\\n", 2); chunk.argv = parts[0].split("\\s+"); if( parts.length>1 && parts[1].length()>0 ){ - chunk.content = parts[1].trim(); + chunk.content = parts[1] + /* reminder: don't trim() here. It would be easier + for Command impls if we did but it makes debug + output look weird. */; } rc.add( chunk ); } @@ -232,17 +235,17 @@ class TestScript { */ public void run(SQLTester tester) throws Exception { final int verbosity = tester.getVerbosity(); - this.setVerbose(verbosity>0); if( null==chunks ){ outer.outln("This test contains content which forces it to be skipped."); }else{ int n = 0; for(CommandChunk chunk : chunks){ if(verbosity>0){ - outer.out("VERBOSE ",moduleName," #",++n," ",chunk.argv[0], + outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),moduleName, + " #",++n," ",chunk.argv[0], " ",Util.argvToString(chunk.argv)); if(verbosity>1 && null!=chunk.content){ - outer.out("\nwith content: ", chunk.content); + outer.out("\n", chunk.content); } outer.out("\n"); } diff --git a/manifest b/manifest index 0d08b578df..97c6167cc3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Strip\s--oom\scommands\sfrom\sSQLTester\sinput\ssince\s(A)\swe\scan't\scurrently\sdo\sanything\swith\sthem\sand\s(B)\sthey\scan\sappear\sas\sbody\scontent\sof\s--testcase\scommands\sand\sthe\scurrent\sparser\scannot\sdeal\swith\sthat.\sIf\s--verbose\sis\sprovided\sonce,\semit\sthe\sname\sand\sargs\sof\seach\scommand\sas\sit's\srun.\sIf\s--verbose\sis\sused\stwice\sor\smore,\salso\semit\sthe\scommand's\sbody\stext,\sif\sany. -D 2023-08-09T16:29:07.987 +C Tweak\sthe\sSQLTester\s--verbose\sand\sdouble-verbose\soutput\sa\sbit\sfor\slegibility. +D 2023-08-09T16:56:42.764 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -265,9 +265,9 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/Outer.java 3d9c40f8ed58ec0df05ca160986ea06ec84ec1f338b069cfba9604bbba467a01 -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java fbaaaf3f04451a66d8031a6792945fa7396be0e522774f364c2cf1520b38003a -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 91ee32908b837a561cd2820fa49476743823a7b5e13e43ea8fd577357900fd16 +F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d36900bd9d1b7c9ac07d23fe2344b1f422b6556ef40da7ab3f5de457f63a2cf8 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java e0a4b523ba2b207e01684dc65ee8d0212bb1d60242dcdff99c4d8eb326b79628 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 44449866970341076cec355193f89646825270a2f4f4073b55150cc4a55100d4 F ext/jni/src/tests/000_first.test 66dd69f5412b87b063982d1aad6907fbe472bc627ca863ae33145e4f54499b8a F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 217f6e0c9c09c576b09ea59fce085a53d1a133927046102b4d00fd58109efc93 -R cabb506465cd0b0ec946cd352f846b65 +P 0770e8467d4bb9490d9ed6e8a20766ffee7049cc3667db6d036c13fccbb6f3ab +R b42664702ccf0865a1d128b95361cc2b U stephan -Z 928111b17e4d371ea24b2ea94c9fa691 +Z a06e0ef26142b9f73cf2ce24e37e2e96 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d076cf8dbe..5a8256b9f4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0770e8467d4bb9490d9ed6e8a20766ffee7049cc3667db6d036c13fccbb6f3ab \ No newline at end of file +46b79afaafda40cb1f920cc96600adf11e8c688184c9559a08eb86776ccf3663 \ No newline at end of file From 5d2a618340b9c0cf9127b4abe069a3c3d82097f5 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 17:04:58 +0000 Subject: [PATCH 110/148] Move the test command body's trim() back into the TestScript class (to simplify Command-level code) but do it after double-verbose has had the chance to emit it as-is (so that debug output is not mangled by the trim). FossilOrigin-Name: f15ecb68f7ca129478336b462508d2c40ea052b4040facefdbc67b13e6aea99d --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 2 +- .../src/org/sqlite/jni/tester/TestScript.java | 5 ++++- ext/jni/src/tests/000_first.test | 1 + manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index c6805cf540..308a5259dc 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -572,7 +572,7 @@ class GlobCommand extends Command { //t.verbose(argv[0]," SQL =\n",sql); int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, ResultRowMode.ONELINE, sql); - final String result = t.getResultText().trim(); + final String result = t.getResultText(); final String sArgs = Util.argvToString(argv); //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); final String glob = argv[1]; diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 45195d3314..1e55c82fff 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -249,7 +249,10 @@ class TestScript { } outer.out("\n"); } - CommandDispatcher.dispatch(tester, chunk.argv, chunk.content); + CommandDispatcher.dispatch( + tester, chunk.argv, + (null==chunk.content) ? null : chunk.content.trim() + ); } } } diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test index 0382c14ec0..bcd78a27a3 100644 --- a/ext/jni/src/tests/000_first.test +++ b/ext/jni/src/tests/000_first.test @@ -24,6 +24,7 @@ Also from the print command. SELECT '{}', 'f'; SELECT '{ }', 'g'; SELECT '(a-b-c)', '[a-b-c]'; + -- this comment must not cause an error --result {a b} c d e "{}" f "{\011}" g (a-b-c) [a-b-c] --testcase 2 SELECT 123 diff --git a/manifest b/manifest index 97c6167cc3..990dac6e01 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Tweak\sthe\sSQLTester\s--verbose\sand\sdouble-verbose\soutput\sa\sbit\sfor\slegibility. -D 2023-08-09T16:56:42.764 +C Move\sthe\stest\scommand\sbody's\strim()\sback\sinto\sthe\sTestScript\sclass\s(to\ssimplify\sCommand-level\scode)\sbut\sdo\sit\safter\sdouble-verbose\shas\shad\sthe\schance\sto\semit\sit\sas-is\s(so\sthat\sdebug\soutput\sis\snot\smangled\sby\sthe\strim). +D 2023-08-09T17:04:58.035 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,10 +266,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java d36900bd9d1b7c9ac07d23fe2344b1f422b6556ef40da7ab3f5de457f63a2cf8 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java e0a4b523ba2b207e01684dc65ee8d0212bb1d60242dcdff99c4d8eb326b79628 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6753941ce1d344396aa3adf7823fb76130d3f3a0c5f02b57d293370886e057f +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 14f6e5df11b0d0f1357e72c0588171589583cbb8cf212b739d202ea17fdaada7 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 44449866970341076cec355193f89646825270a2f4f4073b55150cc4a55100d4 -F ext/jni/src/tests/000_first.test 66dd69f5412b87b063982d1aad6907fbe472bc627ca863ae33145e4f54499b8a +F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0770e8467d4bb9490d9ed6e8a20766ffee7049cc3667db6d036c13fccbb6f3ab -R b42664702ccf0865a1d128b95361cc2b +P 46b79afaafda40cb1f920cc96600adf11e8c688184c9559a08eb86776ccf3663 +R 95e2df2649b37210ba0f30bd88f841aa U stephan -Z a06e0ef26142b9f73cf2ce24e37e2e96 +Z 6db045beedb4bf3da5e485334e151e20 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 5a8256b9f4..3a62236169 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -46b79afaafda40cb1f920cc96600adf11e8c688184c9559a08eb86776ccf3663 \ No newline at end of file +f15ecb68f7ca129478336b462508d2c40ea052b4040facefdbc67b13e6aea99d \ No newline at end of file From 16e620e07ff5ebdb14827c2be07af651a2a83c5e Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 17:23:52 +0000 Subject: [PATCH 111/148] SQLTester --result command: do not double-{}-wrap error messages and do not throw on db error. FossilOrigin-Name: f7be20f5a62f8970f957e5c3a1d1c6536995df5c078dbac34a44f09682e43945 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 13 ++++++++----- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 308a5259dc..e1a954968b 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -305,10 +305,13 @@ public class SQLTester { } private void appendDbErr(sqlite3 db, StringBuilder sb, int rc){ - sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)) - .append(" {") - .append(escapeSqlValue(sqlite3_errmsg(db))) - .append("}"); + sb.append(org.sqlite.jni.ResultCode.getEntryForInt(rc)).append(' '); + final String msg = escapeSqlValue(sqlite3_errmsg(db)); + if( '{' == msg.charAt(0) ){ + sb.append(msg); + }else{ + sb.append('{').append(msg).append('}'); + } } public int execSql(sqlite3 db, boolean throwOnError, @@ -662,7 +665,7 @@ class ResultCommand extends Command { t.incrementTestCounter(); final String sql = t.takeInputBuffer(); //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, bufferMode, ResultRowMode.ONELINE, sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; if( !result.equals(sArgs) ){ diff --git a/manifest b/manifest index 990dac6e01..ee605046b6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Move\sthe\stest\scommand\sbody's\strim()\sback\sinto\sthe\sTestScript\sclass\s(to\ssimplify\sCommand-level\scode)\sbut\sdo\sit\safter\sdouble-verbose\shas\shad\sthe\schance\sto\semit\sit\sas-is\s(so\sthat\sdebug\soutput\sis\snot\smangled\sby\sthe\strim). -D 2023-08-09T17:04:58.035 +C SQLTester\s--result\scommand:\sdo\snot\sdouble-{}-wrap\serror\smessages\sand\sdo\snot\sthrow\son\sdb\serror. +D 2023-08-09T17:23:52.643 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6753941ce1d344396aa3adf7823fb76130d3f3a0c5f02b57d293370886e057f +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 0869fa25e32295e1742e0ab4c2f20e25e47ab5f6d520ab31b7ea075249781548 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 14f6e5df11b0d0f1357e72c0588171589583cbb8cf212b739d202ea17fdaada7 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 44449866970341076cec355193f89646825270a2f4f4073b55150cc4a55100d4 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 46b79afaafda40cb1f920cc96600adf11e8c688184c9559a08eb86776ccf3663 -R 95e2df2649b37210ba0f30bd88f841aa +P f15ecb68f7ca129478336b462508d2c40ea052b4040facefdbc67b13e6aea99d +R 4f9539ed3a0acc150188dac846fdb1dc U stephan -Z 6db045beedb4bf3da5e485334e151e20 +Z 5640a00c9dc31396afba95f58c2ebdf5 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3a62236169..1a8ba85369 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f15ecb68f7ca129478336b462508d2c40ea052b4040facefdbc67b13e6aea99d \ No newline at end of file +f7be20f5a62f8970f957e5c3a1d1c6536995df5c078dbac34a44f09682e43945 \ No newline at end of file From 7f66a3eeb226b4dc9e264838308893825ce21f55 Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 9 Aug 2023 17:47:34 +0000 Subject: [PATCH 112/148] Add an extra constraint to the test-script-interpreter.md spec. FossilOrigin-Name: 7a07863e082664da2efcf4ecd36785d2583abbda12526cdb643cf1aa0568292e --- .../sqlite/jni/tester/test-script-interpreter.md | 14 ++++++++++---- manifest | 14 +++++++------- manifest.uuid | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index d87ce4494a..95220ed59c 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -34,24 +34,30 @@ script are deleted when the script finishes. processing may continue. The "MODULE_NAME" checking in steps 2 and 3 may optionally be discontinued after sighting a "SCRIPT_MODULE_NAME". - 4. If any line begins with the "\|" (0x7c) character, that indicates that + 4. If any line contains "REQUIRED_PROPERTIES:" and that substring is followed + by any non-whitespace text, then the script is not compatible with this + spec. Processing should stop immediately. In verbose mode, the + interpreter might choose to emit an information message saying that the + test script was abandoned due to unsupported requirement properties. + + 5. If any line begins with the "\|" (0x7c) character, that indicates that the input script is not compatible with this specification. Processing of the script should stop immediately. In verbose mode, the interpreter might choose to emit an informational message indicating that the test script was abandoned because it contained "a dbtotxt format database specification". - 5. Any line that begins with "#" is a C-preprocessor line. The interpreter + 6. Any line that begins with "#" is a C-preprocessor line. The interpreter described by this spec does not know how to deal with C-preprocessor lines. Hence, processing should be abandoned. In verbose mode, the interpreter might emit an informational message similar to "script NAME abandoned due to C-preprocessor line: ..." - 6. If a line begins with exactly two minus signs followed by a + 7. If a line begins with exactly two minus signs followed by a lowercase letter, that is a command. Process commands as described below. - 7. All other lines should be accumulated into the "input buffer". + 8. All other lines should be accumulated into the "input buffer". The various commands will have access to this input buffer. Some commands will reset the buffer. diff --git a/manifest b/manifest index ee605046b6..95f62e5064 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C SQLTester\s--result\scommand:\sdo\snot\sdouble-{}-wrap\serror\smessages\sand\sdo\snot\sthrow\son\sdb\serror. -D 2023-08-09T17:23:52.643 +C Add\san\sextra\sconstraint\sto\sthe\stest-script-interpreter.md\sspec. +D 2023-08-09T17:47:34.119 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -268,7 +268,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e8597 F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 0869fa25e32295e1742e0ab4c2f20e25e47ab5f6d520ab31b7ea075249781548 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 14f6e5df11b0d0f1357e72c0588171589583cbb8cf212b739d202ea17fdaada7 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md 44449866970341076cec355193f89646825270a2f4f4073b55150cc4a55100d4 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f15ecb68f7ca129478336b462508d2c40ea052b4040facefdbc67b13e6aea99d -R 4f9539ed3a0acc150188dac846fdb1dc -U stephan -Z 5640a00c9dc31396afba95f58c2ebdf5 +P f7be20f5a62f8970f957e5c3a1d1c6536995df5c078dbac34a44f09682e43945 +R 70c20d78683e221888f80b9ab49a4671 +U drh +Z f9e5e74163f97c2d031b2bb412396edf # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1a8ba85369..e622c3f2f5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f7be20f5a62f8970f957e5c3a1d1c6536995df5c078dbac34a44f09682e43945 \ No newline at end of file +7a07863e082664da2efcf4ecd36785d2583abbda12526cdb643cf1aa0568292e \ No newline at end of file From e0fb44090b5ab20e3f9cf4a3c7b8358ce27b0d9f Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 18:25:50 +0000 Subject: [PATCH 113/148] Adapt TestScript to skip REQUIRED_PROPERTIES, per [7a07863e082664da], and improve a couple of adject reasons-for-skipping messages. FossilOrigin-Name: f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c --- .../src/org/sqlite/jni/tester/TestScript.java | 22 ++++++++++++++----- manifest | 14 ++++++------ manifest.uuid | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 1e55c82fff..d1cdf1e238 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -28,6 +28,7 @@ class TestScript { private List chunks = null; private final Outer outer = new Outer(); private String ignoreReason = null; + private byte[] baScript = null; /* One "chunk" of input, representing a single command and its optional body content. */ @@ -46,8 +47,10 @@ class TestScript { */ public TestScript(String filename) throws Exception{ name = filename; - setContent(new String(readFile(filename), - java.nio.charset.StandardCharsets.UTF_8)); + baScript = readFile(filename); + setContent(new String( + baScript, java.nio.charset.StandardCharsets.UTF_8 + )); } /** @@ -94,6 +97,8 @@ class TestScript { private static final Pattern patternHashLine = Pattern.compile("^#", Pattern.MULTILINE); + private static final Pattern patternRequiredProperties = + Pattern.compile("REQUIRED_PROPERTIES:[ \\t]*(\\S+\\s*)\\n"); /** Returns true if the given script content should be ignored (because it contains certain content which indicates such). @@ -105,9 +110,11 @@ class TestScript { }else if( content.indexOf("\n|")>=0 ){ ignoreReason = "Contains newline-pipe combination."; return true; - }else if( content.indexOf(" MODULE_NAME:")>=0 || - content.indexOf("MIXED_MODULE_NAME:")>=0 ){ - ignoreReason = "Incompatible module script."; + }else if( content.indexOf(" MODULE_NAME:")>=0 ){ + ignoreReason = "Contains MODULE_NAME."; + return true; + }else if( content.indexOf("MIXED_MODULE_NAME:")>=0 ){ + ignoreReason = "Contains MIXED_MODULE_NAME."; return true; } Matcher m = patternHashLine.matcher(content); @@ -115,6 +122,11 @@ class TestScript { ignoreReason = "C-preprocessor line found."; return true; } + m = patternRequiredProperties.matcher(content); + if( m.find() ){ + ignoreReason = "REQUIRED_PROPERTIES found: "+m.group(1).trim(); + return true; + } return false; } diff --git a/manifest b/manifest index 95f62e5064..7e75d8929b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\san\sextra\sconstraint\sto\sthe\stest-script-interpreter.md\sspec. -D 2023-08-09T17:47:34.119 +C Adapt\sTestScript\sto\sskip\sREQUIRED_PROPERTIES,\sper\s[7a07863e082664da],\sand\simprove\sa\scouple\sof\sadject\sreasons-for-skipping\smessages. +D 2023-08-09T18:25:50.693 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449 F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 0869fa25e32295e1742e0ab4c2f20e25e47ab5f6d520ab31b7ea075249781548 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 14f6e5df11b0d0f1357e72c0588171589583cbb8cf212b739d202ea17fdaada7 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 18f55e1e3001c4ccfc359d57448729227c3eaf4a7c774964fe6418e07aefd541 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f7be20f5a62f8970f957e5c3a1d1c6536995df5c078dbac34a44f09682e43945 -R 70c20d78683e221888f80b9ab49a4671 -U drh -Z f9e5e74163f97c2d031b2bb412396edf +P 7a07863e082664da2efcf4ecd36785d2583abbda12526cdb643cf1aa0568292e +R 2aec460c6025f35f30c5bc23045915cc +U stephan +Z c1a2fe06f34d73f23c401dc70e624dbe # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e622c3f2f5..b91866951b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7a07863e082664da2efcf4ecd36785d2583abbda12526cdb643cf1aa0568292e \ No newline at end of file +f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c \ No newline at end of file From e41c09ac8c165becd9e3a18ca41b5146a6e033ce Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 19:51:39 +0000 Subject: [PATCH 114/148] Initial sketches of a line-by-line parser for SQLTester to overcome its compatibility shortcomings. Far from complete. FossilOrigin-Name: 43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9 --- ext/jni/GNUmakefile | 1 + .../src/org/sqlite/jni/tester/SQLTester.java | 33 +++- .../org/sqlite/jni/tester/TestScript2.java | 166 ++++++++++++++++++ ext/jni/src/tests/000-000-sanity.test2 | 10 ++ manifest | 16 +- manifest.uuid | 2 +- 6 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 ext/jni/src/org/sqlite/jni/tester/TestScript2.java create mode 100644 ext/jni/src/tests/000-000-sanity.test2 diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 9d78f9b0e6..6d7f35f900 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -87,6 +87,7 @@ JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ Outer.java \ SQLTester.java \ TestScript.java \ + TestScript2.java \ ) CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index e1a954968b..e93fd10a70 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -80,6 +80,7 @@ public class SQLTester { private int iCurrentDb = 0; private final String initialDbName = "test.db"; private TestScript currentScript; + private TestScript2 currentScript2; public SQLTester(){ reset(); @@ -155,8 +156,32 @@ public class SQLTester { Util.unlink(initialDbName); } + + //! Not yet funcional + private void runTests2() throws Exception { + try { + for(String f : listInFiles){ + reset(); + setupInitialDb(); + ++nTestFile; + final TestScript2 ts = new TestScript2(f); + currentScript2 = ts; + try{ + ts.run(this); + }catch(SkipTestRemainder e){ + /* not an error */ + ++nAbortedScript; + } + outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); + } + }finally{ + currentScript2 = null; + } + Util.unlink(initialDbName); + } + private StringBuilder clearBuffer(StringBuilder b){ - b.delete(0, b.length()); + b.setLength(0);; return b; } @@ -403,11 +428,14 @@ public class SQLTester { public static void main(String[] argv) throws Exception{ final SQLTester t = new SQLTester(); + boolean v2 = false; for(String a : argv){ if(a.startsWith("-")){ final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ t.setVerbosity(t.getVerbosity() + 1); + }else if( flag.equals("2") ){ + v2 = true; }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } @@ -415,7 +443,8 @@ public class SQLTester { } t.addTestScript(a); } - t.runTests(); + if( v2 ) t.runTests2(); + else t.runTests(); t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); if( t.nAbortedScript > 0 ){ t.outln("Aborted ",t.nAbortedScript," script(s)."); diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java new file mode 100644 index 0000000000..4c9c5bdb3b --- /dev/null +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java @@ -0,0 +1,166 @@ +/* +** 2023-08-08 +** +** 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 the TestScript2 part of the SQLTester framework. +*/ +package org.sqlite.jni.tester; +//import java.util.regex.*; +import java.util.Arrays; +import java.nio.charset.StandardCharsets; + +/** + This class represents a single test script. It handles (or + delegates) its the reading-in and parsing, but the details of + evaluation are delegated elsewhere. +*/ +class TestScript2 { + private String filename = null; + private final Cursor curs = new Cursor(); + private final Outer outer = new Outer(); + + private static final class Cursor { + private final StringBuilder sb = new StringBuilder(); + byte[] src = null; + int pos = 0; + int lineNo = 1; + boolean inComment = false; + + void reset(){ + sb.setLength(0); pos = 0; lineNo = 1; inComment = false; + } + } + + private byte[] readFile(String filename) throws Exception { + return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); + } + + /** + Initializes the script with the content of the given file. + Throws if it cannot read the file. + */ + public TestScript2(String filename) throws Exception{ + this.filename = filename; + setVerbosity(2); + curs.src = readFile(filename); + } + + public String getFilename(){ + return filename; + } + + public void setVerbosity(int level){ + outer.setVerbosity(level); + } + + @SuppressWarnings("unchecked") + private TestScript2 verbose(T... vals){ + outer.verbose(vals); + return this; + } + + @SuppressWarnings("unchecked") + private void tossSyntax(Object... msg){ + StringBuilder sb = new StringBuilder(); + sb.append(this.filename).append(":").append(curs.lineNo). + append(": "); + for(Object o : msg) sb.append(o); + throw new RuntimeException(sb.toString()); + } + + private void reset(){ + curs.reset(); + } + + /** + Returns the next line from the buffer, minus the trailing EOL. + If skipLeadingWs is true then all leading whitespace (including + blank links) is skipped over and will not appear in the resulting + string. + + Returns null when all input is consumed. Throws if it reads + illegally-encoded input, e.g. (non-)characters in the range + 128-256. + */ + String getLine(boolean skipLeadingWs){ + curs.sb.setLength(0); + byte b = 0, prevB = 0; + int i = curs.pos; + if(skipLeadingWs) { + /* Skip any leading spaces, including newlines. This will eliminate + blank lines. */ + for(; i < curs.src.length; ++i, prevB=b){ + b = curs.src[i]; + switch((int)b){ + case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; + case 10/*NL*/: ++curs.lineNo; continue; + default: break; + } + break; + } + } + if( i==curs.src.length ){ + return null /* EOF */; + } + boolean doBreak = false; + final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; + int nChar = 0 /* number of bytes in the char */; + for(; i < curs.src.length && !doBreak; ++i){ + b = curs.src[i]; + switch( (int)b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++curs.lineNo; + if(curs.sb.length()>0) doBreak = true; + break; + default: + /* Multi-byte chars need to be gathered up and appended at + one time. Appending individual bytes to the StringBuffer + appends their integer value. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) tossSyntax("Invalid character (#"+(int)b+")."); + break; + } + if( 1==nChar ){ + curs.sb.append((char)b); + }else{ + for(int x = 0; x < nChar; ++x) aChar[x] = curs.src[i+x]; + curs.sb.append(new String(Arrays.copyOf(aChar, nChar), + StandardCharsets.UTF_8)); + i += nChar-1; + } + break; + } + } + curs.pos = i; + if( 0==curs.sb.length() && i==curs.src.length ){ + return null /* EOF */; + } + return curs.sb.toString(); + }/*getLine()*/ + + /** + Runs this test script in the context of the given tester object. + */ + @SuppressWarnings("unchecked") + public void run(SQLTester tester) throws Exception { + reset(); + setVerbosity(tester.getVerbosity()); + String line; + while( null != (line = getLine(false)) ){ + verbose("LINE #",curs.lineNo-1,": ",line); + } + } +} diff --git a/ext/jni/src/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test2 new file mode 100644 index 0000000000..24e1b68a14 --- /dev/null +++ b/ext/jni/src/tests/000-000-sanity.test2 @@ -0,0 +1,10 @@ + /* +** This is a comment. There are many like it but this one is mine. +** + */ +--command 1 + + +--command 🤩😃 + SELECT 1; + SELECT 2; diff --git a/manifest b/manifest index 7e75d8929b..e48221f32f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Adapt\sTestScript\sto\sskip\sREQUIRED_PROPERTIES,\sper\s[7a07863e082664da],\sand\simprove\sa\scouple\sof\sadject\sreasons-for-skipping\smessages. -D 2023-08-09T18:25:50.693 +C Initial\ssketches\sof\sa\sline-by-line\sparser\sfor\sSQLTester\sto\sovercome\sits\scompatibility\sshortcomings.\sFar\sfrom\scomplete. +D 2023-08-09T19:51:39.077 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,7 @@ 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 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 +F ext/jni/GNUmakefile d7300b7e124214afde7f11bddd5c0d336a9be0220fe2b74e787078e1aa2db778 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520 F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328 @@ -266,9 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 0869fa25e32295e1742e0ab4c2f20e25e47ab5f6d520ab31b7ea075249781548 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 35ea65f416b41b1e21119f58109e35518a39cbc1edbdd8883cb581772665c18d F ext/jni/src/org/sqlite/jni/tester/TestScript.java 18f55e1e3001c4ccfc359d57448729227c3eaf4a7c774964fe6418e07aefd541 +F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 1c8426039f2050cf623dab17e5d05a976ee061429c7be1eb5a3e73f7b00daf65 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba +F ext/jni/src/tests/000-000-sanity.test2 0b4bb90e12cfa96e24999f5890ce46305b6a83efcf9ef0ff4df16521f636a9d5 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2090,8 +2092,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7a07863e082664da2efcf4ecd36785d2583abbda12526cdb643cf1aa0568292e -R 2aec460c6025f35f30c5bc23045915cc +P f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c +R e3dd73f0afa45739c2d0daea22589f05 U stephan -Z c1a2fe06f34d73f23c401dc70e624dbe +Z 24fe8a8f37b2f666f1b6b1c10180bf33 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b91866951b..eaf5ea6314 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c \ No newline at end of file +43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9 \ No newline at end of file From bef0369ff463ff634c60f8d75be387e573be51ef Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 22:18:22 +0000 Subject: [PATCH 115/148] More for the SQLTester rework. Can read input and dispatch commands, but only --print is currently implemented. FossilOrigin-Name: 4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314 --- .../src/org/sqlite/jni/tester/SQLTester.java | 43 ++- .../src/org/sqlite/jni/tester/TestScript.java | 2 +- .../org/sqlite/jni/tester/TestScript2.java | 343 ++++++++++++++++-- ext/jni/src/tests/000-000-sanity.test2 | 16 +- manifest | 18 +- manifest.uuid | 2 +- 6 files changed, 358 insertions(+), 66 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index e93fd10a70..39047fa98e 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -80,7 +80,6 @@ public class SQLTester { private int iCurrentDb = 0; private final String initialDbName = "test.db"; private TestScript currentScript; - private TestScript2 currentScript2; public SQLTester(){ reset(); @@ -159,23 +158,23 @@ public class SQLTester { //! Not yet funcional private void runTests2() throws Exception { - try { - for(String f : listInFiles){ - reset(); - setupInitialDb(); - ++nTestFile; - final TestScript2 ts = new TestScript2(f); - currentScript2 = ts; - try{ - ts.run(this); - }catch(SkipTestRemainder e){ - /* not an error */ - ++nAbortedScript; - } - outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); + for(String f : listInFiles){ + reset(); + setupInitialDb(); + ++nTestFile; + final TestScript2 ts = new TestScript2(f); + try{ + ts.run(this); + }catch(SkipTestRemainder2 e){ + /* not fatal */ + outln(e); + ++nAbortedScript; + }catch(IncompatibleDirective e){ + /* not fatal */ + outln(e); + ++nAbortedScript; } - }finally{ - currentScript2 = null; + outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); } Util.unlink(initialDbName); } @@ -195,6 +194,16 @@ public class SQLTester { StringBuilder getInputBuffer(){ return inputBuffer; } + void appendInput(String n, boolean addNL){ + inputBuffer.append(n); + if(addNL) inputBuffer.append('\n'); + } + + void appendResult(String n, boolean addNL){ + resultBuffer.append(n); + if(addNL) resultBuffer.append('\n'); + } + String getInputText(){ return inputBuffer.toString(); } String getResultText(){ return resultBuffer.toString(); } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index d1cdf1e238..742cf4fa52 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -90,7 +90,7 @@ class TestScript { } @SuppressWarnings("unchecked") - private TestScript verbose(T... vals){ + private TestScript verbose(Object... vals){ outer.verbose(vals); return this; } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java index 4c9c5bdb3b..013c46cd12 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java @@ -12,9 +12,123 @@ ** This file contains the TestScript2 part of the SQLTester framework. */ package org.sqlite.jni.tester; -//import java.util.regex.*; import java.util.Arrays; import java.nio.charset.StandardCharsets; +import java.util.regex.*; + +class SQLTestException extends RuntimeException { + public SQLTestException(String msg){ + super(msg); + } +} + +class SkipTestRemainder2 extends SQLTestException { + public SkipTestRemainder2(TestScript2 ts){ + super(ts.getOutputPrefix()+": skipping remainder"); + } +} + +class IncompatibleDirective extends SQLTestException { + public IncompatibleDirective(TestScript2 ts, String line){ + super(ts.getOutputPrefix()+": incompatible directive: "+line); + } +} + +class UnknownCommand extends SQLTestException { + public UnknownCommand(TestScript2 ts, String line){ + super(ts.getOutputPrefix()+": unknown command: "+line); + } +} + +abstract class Command2 { + protected Command2(){} + + public abstract void process( + SQLTester st, TestScript2 ts, String[] argv + ) throws Exception; + + /** + If argv.length-1 (-1 because the command's name is in argv[0]) does not + fall in the inclusive range (min,max) then this function throws. Use + a max value of -1 to mean unlimited. + */ + protected final void argcCheck(String[] argv, int min, int max) throws Exception{ + int argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + Util.badArg(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + Util.badArg(argv[0]," requires ",min,"-",max," arguments."); + }else{ + Util.badArg(argv[0]," requires at least ",min," arguments."); + } + } + } + + /** + Equivalent to argcCheck(argv,argc,argc). + */ + protected final void argcCheck(String[] argv, int argc) throws Exception{ + argcCheck(argv, argc, argc); + } +} + +class PrintCommand2 extends Command2 { + public void process( + SQLTester st, TestScript2 ts, String[] argv + ) throws Exception{ + st.out(ts.getOutputPrefix(),": "); + if( 1==argv.length ){ + st.outln( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + final String body = ts.fetchCommandBody(); + if( null!=body ){ + st.out(body,"\n"); + } + } +} + +class CommandDispatcher2 { + + private static java.util.Map commandMap = + new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ + static Command2 getCommandByName(String name){ + Command2 rv = commandMap.get(name); + if( null!=rv ) return rv; + switch(name){ + case "print": rv = new PrintCommand2(); break; + default: rv = null; break; + } + if( null!=rv ) commandMap.put(name, rv); + return rv; + } + + /** + Treats argv[0] as a command name, looks it up with + getCommandByName(), and calls process() on that instance, passing + it arguments given to this function. + */ + static void dispatch(SQLTester tester, TestScript2 ts, String[] argv) throws Exception{ + final Command2 cmd = getCommandByName(argv[0]); + if(null == cmd){ + if( tester.skipUnknownCommands() ){ + ts.warn("skipping remainder because of unknown command '",argv[0],"'."); + throw new SkipTestRemainder2(ts); + } + Util.toss(IllegalArgumentException.class, + ts.getOutputPrefix()+": no command handler found for '"+argv[0]+"'."); + } + cmd.process(tester, ts, argv); + } +} + /** This class represents a single test script. It handles (or @@ -23,18 +137,23 @@ import java.nio.charset.StandardCharsets; */ class TestScript2 { private String filename = null; - private final Cursor curs = new Cursor(); + private String moduleName = null; + private final Cursor cur = new Cursor(); private final Outer outer = new Outer(); private static final class Cursor { private final StringBuilder sb = new StringBuilder(); byte[] src = null; int pos = 0; - int lineNo = 1; + int putbackPos = 0; + int putbackLineNo = 0; + int lineNo = 0 /* yes, zero */; + int peekedPos = 0; + int peekedLineNo = 0; boolean inComment = false; void reset(){ - sb.setLength(0); pos = 0; lineNo = 1; inComment = false; + sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false; } } @@ -49,76 +168,102 @@ class TestScript2 { public TestScript2(String filename) throws Exception{ this.filename = filename; setVerbosity(2); - curs.src = readFile(filename); + cur.src = readFile(filename); } public String getFilename(){ return filename; } + public String getModuleName(){ + return moduleName; + } + public void setVerbosity(int level){ outer.setVerbosity(level); } + public String getOutputPrefix(){ + return "["+(moduleName==null ? filename : moduleName)+"] line "+ + cur.lineNo; + } + @SuppressWarnings("unchecked") - private TestScript2 verbose(T... vals){ - outer.verbose(vals); + private TestScript2 verbose(Object... vals){ + final int verbosity = outer.getVerbosity(); + if(verbosity>0){ + outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), + getOutputPrefix(),": "); + outer.outln(vals); + } + return this; + } + + @SuppressWarnings("unchecked") + public TestScript2 warn(Object... vals){ + outer.out("WARNING ", getOutputPrefix(),": "); + outer.outln(vals); return this; } @SuppressWarnings("unchecked") private void tossSyntax(Object... msg){ StringBuilder sb = new StringBuilder(); - sb.append(this.filename).append(":").append(curs.lineNo). + sb.append(this.filename).append(":").append(cur.lineNo). append(": "); for(Object o : msg) sb.append(o); throw new RuntimeException(sb.toString()); } private void reset(){ - curs.reset(); + cur.reset(); } + /** Returns the next line from the buffer, minus the trailing EOL. - If skipLeadingWs is true then all leading whitespace (including - blank links) is skipped over and will not appear in the resulting - string. Returns null when all input is consumed. Throws if it reads illegally-encoded input, e.g. (non-)characters in the range 128-256. */ - String getLine(boolean skipLeadingWs){ - curs.sb.setLength(0); + String getLine(){ + if( cur.pos==cur.src.length ){ + return null /* EOF */; + } + cur.putbackPos = cur.pos; + cur.putbackLineNo = cur.lineNo; + cur.sb.setLength(0); + final boolean skipLeadingWs = false; byte b = 0, prevB = 0; - int i = curs.pos; + int i = cur.pos; if(skipLeadingWs) { /* Skip any leading spaces, including newlines. This will eliminate blank lines. */ - for(; i < curs.src.length; ++i, prevB=b){ - b = curs.src[i]; + for(; i < cur.src.length; ++i, prevB=b){ + b = cur.src[i]; switch((int)b){ case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; - case 10/*NL*/: ++curs.lineNo; continue; + case 10/*NL*/: ++cur.lineNo; continue; default: break; } break; } - } - if( i==curs.src.length ){ - return null /* EOF */; + if( i==cur.src.length ){ + return null /* EOF */; + } } boolean doBreak = false; final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; int nChar = 0 /* number of bytes in the char */; - for(; i < curs.src.length && !doBreak; ++i){ - b = curs.src[i]; + for(; i < cur.src.length && !doBreak; ++i){ + b = cur.src[i]; switch( (int)b ){ case 13/*CR*/: continue; case 10/*NL*/: - ++curs.lineNo; - if(curs.sb.length()>0) doBreak = true; + ++cur.lineNo; + if(cur.sb.length()>0) doBreak = true; + // Else it's an empty string break; default: /* Multi-byte chars need to be gathered up and appended at @@ -134,33 +279,161 @@ class TestScript2 { break; } if( 1==nChar ){ - curs.sb.append((char)b); + cur.sb.append((char)b); }else{ - for(int x = 0; x < nChar; ++x) aChar[x] = curs.src[i+x]; - curs.sb.append(new String(Arrays.copyOf(aChar, nChar), + for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; + cur.sb.append(new String(Arrays.copyOf(aChar, nChar), StandardCharsets.UTF_8)); i += nChar-1; } break; } } - curs.pos = i; - if( 0==curs.sb.length() && i==curs.src.length ){ + cur.pos = i; + final String rv = cur.sb.toString(); + if( i==cur.src.length && 0==rv.length() ){ return null /* EOF */; } - return curs.sb.toString(); + return rv; }/*getLine()*/ + /** + Fetches the next line then resets the cursor to its pre-call + state. consumePeeked() can be used to consume this peeked line + without having to re-parse it. + */ + public String peekLine(){ + final int oldPos = cur.pos; + final int oldPB = cur.putbackPos; + final int oldPBL = cur.putbackLineNo; + final int oldLine = cur.lineNo; + final String rc = getLine(); + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + return rc; + } + + /** + Only valid after calling peekLine() and before calling getLine(). + This places the cursor to the position it would have been at had + the peekLine() had been fetched with getLine(). + */ + public void consumePeeked(){ + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + public void putbackLine(){ + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + + private static final Pattern patternRequiredProperties = + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(.*+)\\s*$"); + private static final Pattern patternScriptModuleName = + Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternMixedModuleName = + Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternCommand = + Pattern.compile("^--(([a-z-]+)( .*)?)$"); + + /** + Looks for "directives." If a compatible one is found, it is + processed and this function returns. If an incompatible one is found, + a description of it is returned and processing of the test must + end immediately. + */ + private void checkForDirective(String line) throws IncompatibleDirective { + if(line.startsWith("#")){ + throw new IncompatibleDirective(this, "C-preprocessor input: "+line); + }else if(line.startsWith("---")){ + new IncompatibleDirective(this, "Triple-dash: "+line); + } + Matcher m = patternScriptModuleName.matcher(line); + if( m.find() ){ + moduleName = m.group(1); + return; + } + m = patternRequiredProperties.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); + } + m = patternMixedModuleName.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); + } + return; + } + + public boolean isCommandLine(String line){ + final Matcher m = patternCommand.matcher(line); + return m.find(); + } + + /** + If line looks like a command, returns an argv for that command + invocation, else returns null. + */ + public String[] getCommandArgv(String line){ + final Matcher m = patternCommand.matcher(line); + return m.find() ? m.group(1).trim().split("\\s+") : null; + } + + /** + Fetches lines until the next command. Throws if + checkForDirective() does. Returns null if there is no input or + it's only whitespace. The returned string is trim()'d of + leading/trailing whitespace. + */ + public String fetchCommandBody(){ + final StringBuilder sb = new StringBuilder(); + String line; + while( (null != (line = peekLine())) ){ + checkForDirective(line); + if( !isCommandLine(line) ){ + sb.append(line).append("\n"); + consumePeeked(); + }else{ + break; + } + } + line = sb.toString().trim(); + return line.isEmpty() ? null : line; + } + + public void processCommand(SQLTester t, String[] argv) throws Exception{ + //verbose("got argv: ",argv[0], " ", Util.argvToString(argv)); + //verbose("Input buffer = ",t.getInputBuffer()); + CommandDispatcher2.dispatch(t, this, argv); + } + /** Runs this test script in the context of the given tester object. */ @SuppressWarnings("unchecked") - public void run(SQLTester tester) throws Exception { + public boolean run(SQLTester tester) throws Exception { reset(); setVerbosity(tester.getVerbosity()); - String line; - while( null != (line = getLine(false)) ){ - verbose("LINE #",curs.lineNo-1,": ",line); + String line, directive; + String[] argv; + while( null != (line = getLine()) ){ + //verbose(line); + checkForDirective(line); + argv = getCommandArgv(line); + if( null!=argv ){ + processCommand(tester, argv); + continue; + } + tester.appendInput(line,true); } + return true; } } diff --git a/ext/jni/src/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test2 index 24e1b68a14..241305c0f8 100644 --- a/ext/jni/src/tests/000-000-sanity.test2 +++ b/ext/jni/src/tests/000-000-sanity.test2 @@ -1,10 +1,20 @@ /* ** This is a comment. There are many like it but this one is mine. ** - */ ---command 1 +** SCRIPT_MODULE_NAME: sanity-check +** xMIXED_MODULE_NAME: mixed-module +** xMODULE_NAME: module-name +** xREQUIRED_PROPERTIES: small fast reliable +** +*/ +/*#if foo*/ +/*---foo*/ +/* --print without flags dumps current input buffer */ +--print ---command 🤩😃 +non-command/non-directive text after --print is also emitted. +--print 🤩😃 SELECT 1; SELECT 2; +--print the end diff --git a/manifest b/manifest index e48221f32f..93a495d6f2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\ssketches\sof\sa\sline-by-line\sparser\sfor\sSQLTester\sto\sovercome\sits\scompatibility\sshortcomings.\sFar\sfrom\scomplete. -D 2023-08-09T19:51:39.077 +C More\sfor\sthe\sSQLTester\srework.\sCan\sread\sinput\sand\sdispatch\scommands,\sbut\sonly\s--print\sis\scurrently\simplemented. +D 2023-08-09T22:18:22.852 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,11 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 35ea65f416b41b1e21119f58109e35518a39cbc1edbdd8883cb581772665c18d -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 18f55e1e3001c4ccfc359d57448729227c3eaf4a7c774964fe6418e07aefd541 -F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 1c8426039f2050cf623dab17e5d05a976ee061429c7be1eb5a3e73f7b00daf65 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2663dffe3977b73730ba3cbdd6dc0fe053699479759b75bb46c1f966773f0b76 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28 +F ext/jni/src/org/sqlite/jni/tester/TestScript2.java f01faf9facbc029f5baf731edf5de37cdd2921ec55ca4d8bd1e764c13211828f F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test2 0b4bb90e12cfa96e24999f5890ce46305b6a83efcf9ef0ff4df16521f636a9d5 +F ext/jni/src/tests/000-000-sanity.test2 8cb312a3dde4d09ead6a33a056ef685a99225b18635a07d03fa21d83879fb1c0 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2092,8 +2092,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f937097e9b22a6c78c242cbf00c71bdc57f04b1b9a15ae24058bc2813c99688c -R e3dd73f0afa45739c2d0daea22589f05 +P 43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9 +R 02b26ab163237a92c9aeaab8dbe6493d U stephan -Z 24fe8a8f37b2f666f1b6b1c10180bf33 +Z 4fb10f0d619801f5f787d45bc947ea2c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index eaf5ea6314..c6f2dd21f8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9 \ No newline at end of file +4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314 \ No newline at end of file From 35af4c5ff1dfa1f41e9239a74db90c9fa48f486a Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 22:30:10 +0000 Subject: [PATCH 116/148] Correct REQUIRED_PROPERTIES handling to not fail if there are no properties. FossilOrigin-Name: 7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b --- ext/jni/src/org/sqlite/jni/tester/TestScript2.java | 2 +- ext/jni/src/tests/000-000-sanity.test2 | 1 + manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java index 013c46cd12..07533a6d4a 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java @@ -337,7 +337,7 @@ class TestScript2 { } private static final Pattern patternRequiredProperties = - Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(.*+)\\s*$"); + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); private static final Pattern patternScriptModuleName = Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); private static final Pattern patternMixedModuleName = diff --git a/ext/jni/src/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test2 index 241305c0f8..5511291878 100644 --- a/ext/jni/src/tests/000-000-sanity.test2 +++ b/ext/jni/src/tests/000-000-sanity.test2 @@ -5,6 +5,7 @@ ** xMIXED_MODULE_NAME: mixed-module ** xMODULE_NAME: module-name ** xREQUIRED_PROPERTIES: small fast reliable +** REQUIRED_PROPERTIES: ** */ diff --git a/manifest b/manifest index 93a495d6f2..cd7f7de5bb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sfor\sthe\sSQLTester\srework.\sCan\sread\sinput\sand\sdispatch\scommands,\sbut\sonly\s--print\sis\scurrently\simplemented. -D 2023-08-09T22:18:22.852 +C Correct\sREQUIRED_PROPERTIES\shandling\sto\snot\sfail\sif\sthere\sare\sno\sproperties. +D 2023-08-09T22:30:10.275 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -268,9 +268,9 @@ F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e8597 F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2663dffe3977b73730ba3cbdd6dc0fe053699479759b75bb46c1f966773f0b76 F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28 -F ext/jni/src/org/sqlite/jni/tester/TestScript2.java f01faf9facbc029f5baf731edf5de37cdd2921ec55ca4d8bd1e764c13211828f +F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 3fc6700ab92e614f61856eeb87469589e57342cb66f5c4f9de425b45425f278f F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test2 8cb312a3dde4d09ead6a33a056ef685a99225b18635a07d03fa21d83879fb1c0 +F ext/jni/src/tests/000-000-sanity.test2 dca0364ca25dacdff38355870fee51be7112eade930b3597c808c2f88c44a782 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2092,8 +2092,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 43534cd042499c1bef44ca5c4a8305a710d99e70e8b0adce6df50c6a1f0402b9 -R 02b26ab163237a92c9aeaab8dbe6493d +P 4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314 +R c8b3185b3c963903b037db53ffaa5711 U stephan -Z 4fb10f0d619801f5f787d45bc947ea2c +Z f51d1e9a0eee42db59afa92b063b01d4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c6f2dd21f8..c0b4f14f97 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314 \ No newline at end of file +7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b \ No newline at end of file From e35a703b76a62815bb7c3f15e8e3e841a10b0bdd Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 9 Aug 2023 23:47:14 +0000 Subject: [PATCH 117/148] Port the SQLTester 'v1' commands to the 'v2' evaluation bits. Still TODO is swapping out v1 with these separate impls. FossilOrigin-Name: 0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 --- .../src/org/sqlite/jni/tester/SQLTester.java | 2 +- .../org/sqlite/jni/tester/TestScript2.java | 277 ++++++++++++++++-- ext/jni/src/tests/000-000-sanity.test2 | 45 ++- manifest | 16 +- manifest.uuid | 2 +- 5 files changed, 302 insertions(+), 40 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 39047fa98e..03bde86fdd 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -234,7 +234,7 @@ public class SQLTester { sqlite3 getCurrentDb(){ return aDb[iCurrentDb]; } sqlite3 getDbById(int id) throws Exception{ - return affirmDbId(id).aDb[iCurrentDb]; + return affirmDbId(id).aDb[id]; } void closeDb(int id) throws Exception{ diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java index 07533a6d4a..ee85b72bd8 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java @@ -12,6 +12,8 @@ ** This file contains the TestScript2 part of the SQLTester framework. */ package org.sqlite.jni.tester; +import static org.sqlite.jni.SQLite3Jni.*; +import org.sqlite.jni.sqlite3; import java.util.Arrays; import java.nio.charset.StandardCharsets; import java.util.regex.*; @@ -22,6 +24,12 @@ class SQLTestException extends RuntimeException { } } +class TestScript2Failed extends SQLTestException { + public TestScript2Failed(TestScript2 ts, String msg){ + super(ts.getOutputPrefix()+": "+msg); + } +} + class SkipTestRemainder2 extends SQLTestException { public SkipTestRemainder2(TestScript2 ts){ super(ts.getOutputPrefix()+": skipping remainder"); @@ -73,23 +81,221 @@ abstract class Command2 { } } +//! --close command +class CloseDbCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0,1); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + } +} + +//! --db command +class DbCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,1); + t.setCurrentDb( Integer.parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand2 extends Command2 { + private boolean negate = false; + public GlobCommand2(){} + protected GlobCommand2(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText(); + final String sArgs = Util.argvToString(argv); + //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + final String glob = argv[1]; + rc = SQLTester.strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --json command +class JsonCommand2 extends ResultCommand2 { + public JsonCommand2(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand2 extends TableResultCommand2 { + public JsonBlockCommand2(){ super(true); } +} + +//! --new command +class NewDbCommand2 extends OpenDbCommand2 { + public NewDbCommand2(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + } +} + +//! --notglob command +class NotGlobCommand2 extends GlobCommand2 { + public NotGlobCommand2(){ + super(true); + } +} + +//! --null command +class NullCommand2 extends Command2 { + public void process( + SQLTester st, TestScript2 ts, String[] argv + ) throws Exception{ + argcCheck(argv,1); + st.setNullValue( argv[1] ); + } +} + +//! --open command +class OpenDbCommand2 extends Command2 { + private boolean createIfNeeded = false; + public OpenDbCommand2(){} + protected OpenDbCommand2(boolean c){createIfNeeded = c;} + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,1); + t.openDb(argv[1], createIfNeeded); + } +} + +//! --print command class PrintCommand2 extends Command2 { public void process( SQLTester st, TestScript2 ts, String[] argv ) throws Exception{ st.out(ts.getOutputPrefix(),": "); - if( 1==argv.length ){ - st.outln( st.getInputText() ); + final String body = ts.fetchCommandBody(); + if( 1==argv.length && null==body ){ + st.out( st.getInputText() ); }else{ st.outln( Util.argvToString(argv) ); } - final String body = ts.fetchCommandBody(); if( null!=body ){ - st.out(body,"\n"); + st.out(body); } } } +//! --result command +class ResultCommand2 extends Command2 { + private final ResultBufferMode bufferMode; + protected ResultCommand2(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand2(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nargs:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + t.verbose(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand2 extends Command2 { + private final boolean jsonMode; + protected TableResultCommand2(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand2(){ this(false); } + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand2 extends Command2 { + public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ + argcCheck(argv,1); + // TODO?: do something with the test name + final String body = ts.fetchCommandBody(); + t.clearResultBuffer(); + t.clearInputBuffer().append(null==body ? "" : body); + } +} + class CommandDispatcher2 { private static java.util.Map commandMap = @@ -103,7 +309,21 @@ class CommandDispatcher2 { Command2 rv = commandMap.get(name); if( null!=rv ) return rv; switch(name){ + case "close": rv = new CloseDbCommand2(); break; + case "db": rv = new DbCommand2(); break; + case "glob": rv = new GlobCommand2(); break; + case "json": rv = new JsonCommand2(); break; + case "json-block": rv = new JsonBlockCommand2(); break; + case "new": rv = new NewDbCommand2(); break; + case "notglob": rv = new NotGlobCommand2(); break; + case "null": rv = new NullCommand2(); break; + case "oom": rv = new NoopCommand2(); break; + case "open": rv = new OpenDbCommand2(); break; case "print": rv = new PrintCommand2(); break; + case "result": rv = new ResultCommand2(); break; + case "run": rv = new RunCommand2(); break; + case "tableresult": rv = new TableResultCommand2(); break; + case "testcase": rv = new TestCaseCommand2(); break; default: rv = null; break; } if( null!=rv ) commandMap.put(name, rv); @@ -187,11 +407,10 @@ class TestScript2 { return "["+(moduleName==null ? filename : moduleName)+"] line "+ cur.lineNo; } - @SuppressWarnings("unchecked") - private TestScript2 verbose(Object... vals){ + private TestScript2 verboseN(int level, Object... vals){ final int verbosity = outer.getVerbosity(); - if(verbosity>0){ + if(verbosity>=level){ outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), getOutputPrefix(),": "); outer.outln(vals); @@ -199,6 +418,9 @@ class TestScript2 { return this; } + private TestScript2 verbose1(Object... vals){return verboseN(1,vals);} + private TestScript2 verbose2(Object... vals){return verboseN(2,vals);} + @SuppressWarnings("unchecked") public TestScript2 warn(Object... vals){ outer.out("WARNING ", getOutputPrefix(),": "); @@ -355,7 +577,7 @@ class TestScript2 { if(line.startsWith("#")){ throw new IncompatibleDirective(this, "C-preprocessor input: "+line); }else if(line.startsWith("---")){ - new IncompatibleDirective(this, "Triple-dash: "+line); + new IncompatibleDirective(this, "triple-dash: "+line); } Matcher m = patternScriptModuleName.matcher(line); if( m.find() ){ @@ -370,12 +592,19 @@ class TestScript2 { if( m.find() ){ throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } return; } - public boolean isCommandLine(String line){ + public boolean isCommandLine(String line, boolean checkForImpl){ final Matcher m = patternCommand.matcher(line); - return m.find(); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); + } + return rc; } /** @@ -388,33 +617,45 @@ class TestScript2 { } /** - Fetches lines until the next command. Throws if + Fetches lines until the next recognized command. Throws if checkForDirective() does. Returns null if there is no input or - it's only whitespace. The returned string is trim()'d of - leading/trailing whitespace. + it's only whitespace. The returned string retains all whitespace. + + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. */ public String fetchCommandBody(){ final StringBuilder sb = new StringBuilder(); String line; while( (null != (line = peekLine())) ){ checkForDirective(line); - if( !isCommandLine(line) ){ + if( !isCommandLine(line, true) ){ sb.append(line).append("\n"); consumePeeked(); }else{ break; } } - line = sb.toString().trim(); - return line.isEmpty() ? null : line; + line = sb.toString(); + return line.trim().isEmpty() ? null : line; } public void processCommand(SQLTester t, String[] argv) throws Exception{ - //verbose("got argv: ",argv[0], " ", Util.argvToString(argv)); - //verbose("Input buffer = ",t.getInputBuffer()); + verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); + if(outer.getVerbosity()>1){ + final String input = t.getInputText(); + if( !input.isEmpty() ) verbose2("Input buffer = ",input); + } CommandDispatcher2.dispatch(t, this, argv); } + public void toss(Object... msg) throws TestScript2Failed { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + throw new TestScript2Failed(this, sb.toString()); + } + /** Runs this test script in the context of the given tester object. */ diff --git a/ext/jni/src/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test2 index 5511291878..3ad4c39680 100644 --- a/ext/jni/src/tests/000-000-sanity.test2 +++ b/ext/jni/src/tests/000-000-sanity.test2 @@ -1,4 +1,4 @@ - /* +/* ** This is a comment. There are many like it but this one is mine. ** ** SCRIPT_MODULE_NAME: sanity-check @@ -8,14 +8,35 @@ ** REQUIRED_PROPERTIES: ** */ - -/*#if foo*/ -/*---foo*/ -/* --print without flags dumps current input buffer */ ---print - -non-command/non-directive text after --print is also emitted. ---print 🤩😃 - SELECT 1; - SELECT 2; ---print the end +--close all +--oom +--db 0 +--new my.db +--null zilch +--testcase 1.0 +SELECT 1, null; +--result 1 zilch +--glob *zil* +--notglob *ZIL* +SELECT 1, 2; +intentional error; +--run +--testcase json-1 +SELECT json_array(1,2,3) +--json [1,2,3] +--testcase tableresult-1 + select 1, 'a'; + select 2, 'b'; +--tableresult + # [a-z] + 2 b +--end +--testcase json-block-1 + select json_array(1,2,3); + select json_object('a',1,'b',2); +--json-block + [1,2,3] + {"a":1,"b":2} +--end +--close +--print 🤩😃 the end diff --git a/manifest b/manifest index cd7f7de5bb..8119a67605 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Correct\sREQUIRED_PROPERTIES\shandling\sto\snot\sfail\sif\sthere\sare\sno\sproperties. -D 2023-08-09T22:30:10.275 +C Port\sthe\sSQLTester\s'v1'\scommands\sto\sthe\s'v2'\sevaluation\sbits.\sStill\sTODO\sis\sswapping\sout\sv1\swith\sthese\sseparate\simpls. +D 2023-08-09T23:47:14.521 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,11 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 2663dffe3977b73730ba3cbdd6dc0fe053699479759b75bb46c1f966773f0b76 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ae38d872d2cb582e1a1abd67b5e9c276bf2f610cacc918428b63c668131642e F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28 -F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 3fc6700ab92e614f61856eeb87469589e57342cb66f5c4f9de425b45425f278f +F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 25895a534a1e4634268beecd1a689bdfc0aafbfe32071c27b5189ccb8aeec31e F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test2 dca0364ca25dacdff38355870fee51be7112eade930b3597c808c2f88c44a782 +F ext/jni/src/tests/000-000-sanity.test2 dfbcccc7b3548ae56deb2ef8fe17dd9235a81fbd552536ef9202284549c7fcf3 F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2092,8 +2092,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4fa2ad33edbcef393dd98dbf90586ad8f32ec0beab02f197c8038a44be86c314 -R c8b3185b3c963903b037db53ffaa5711 +P 7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b +R eed3d04c8636ba991a620a2a8f5a013d U stephan -Z f51d1e9a0eee42db59afa92b063b01d4 +Z bdeddd00da26b413ab473bf39ad731fc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index c0b4f14f97..d522acb938 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b \ No newline at end of file +0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 \ No newline at end of file From 84c1a7b49f8fa7c499403cbd8733153718a16620 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 00:34:38 +0000 Subject: [PATCH 118/148] Replace the SQLTester infrastructure with a line-oriented, non-regex-heavy parser. Add --column-names command. FossilOrigin-Name: 88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d --- ext/jni/GNUmakefile | 1 - .../src/org/sqlite/jni/tester/SQLTester.java | 575 +++--------- .../src/org/sqlite/jni/tester/TestScript.java | 823 +++++++++++++----- .../org/sqlite/jni/tester/TestScript2.java | 680 --------------- ...0-000-sanity.test2 => 000-000-sanity.test} | 11 +- ...{010_ignored.test => 000-001-ignored.test} | 0 ext/jni/src/tests/000_first.test | 68 -- manifest | 22 +- manifest.uuid | 2 +- 9 files changed, 743 insertions(+), 1439 deletions(-) delete mode 100644 ext/jni/src/org/sqlite/jni/tester/TestScript2.java rename ext/jni/src/tests/{000-000-sanity.test2 => 000-000-sanity.test} (76%) rename ext/jni/src/tests/{010_ignored.test => 000-001-ignored.test} (100%) delete mode 100644 ext/jni/src/tests/000_first.test diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 6d7f35f900..9d78f9b0e6 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -87,7 +87,6 @@ JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ Outer.java \ SQLTester.java \ TestScript.java \ - TestScript2.java \ ) CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 03bde86fdd..431679c7ce 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -21,19 +21,6 @@ import java.util.regex.*; import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; -class TestFailure extends RuntimeException { - public TestFailure(String msg){ - super(msg); - } -} - -class SkipTestRemainder extends RuntimeException { - public TestScript testScript; - public SkipTestRemainder(TestScript ts){ - super("Skipping remainder of "+ts.getName()); - testScript = ts; - } -} /** Modes for how to handle SQLTester.execSql()'s @@ -55,6 +42,12 @@ enum ResultRowMode { NEWLINE }; +class SQLTesterException extends RuntimeException { + public SQLTesterException(String msg){ + super(msg); + } +} + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -76,6 +69,7 @@ public class SQLTester { private int nTestFile = 0; private int nAbortedScript = 0; private int nTest; + private boolean emitColNames; private final sqlite3[] aDb = new sqlite3[7]; private int iCurrentDb = 0; private final String initialDbName = "test.db"; @@ -95,6 +89,8 @@ public class SQLTester { return this.outer.isVerbose(); } + void outputColumnNames(boolean b){ emitColNames = b; } + @SuppressWarnings("unchecked") public void verbose(Object... vals){ outer.verbose(vals); @@ -125,56 +121,29 @@ public class SQLTester { return currentScript; } - public void runTests() throws Exception { - // process each input file - try { - for(String f : listInFiles){ - reset(); - setupInitialDb(); - ++nTestFile; - final TestScript ts = new TestScript(f); - currentScript = ts; - outln("----->>>>> ",ts.getModuleName()," [",ts.getName(),"]"); - if( ts.isIgnored() ){ - outln("WARNING: skipping [",ts.getModuleName(),"]: ", - ts.getIgnoredReason()); - continue; - }else{ - try{ - ts.run(this); - }catch(SkipTestRemainder e){ - /* not an error */ - ++nAbortedScript; - } - } - outln("<<<<<----- ",ts.getModuleName(),": ",nTest," test(s)"); - } - }finally{ - currentScript = null; - } - Util.unlink(initialDbName); - } - - - //! Not yet funcional - private void runTests2() throws Exception { + private void runTests() throws Exception { for(String f : listInFiles){ reset(); setupInitialDb(); ++nTestFile; - final TestScript2 ts = new TestScript2(f); + final TestScript ts = new TestScript(f); + outln("----->>>>> running [",f,"]"); try{ ts.run(this); - }catch(SkipTestRemainder2 e){ - /* not fatal */ + }catch(UnknownCommand e){ + /* currently not fatal */ outln(e); ++nAbortedScript; }catch(IncompatibleDirective e){ /* not fatal */ outln(e); ++nAbortedScript; + }catch(Exception e){ + ++nAbortedScript; + throw e; + }finally{ + outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); } - outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); } Util.unlink(initialDbName); } @@ -264,7 +233,7 @@ public class SQLTester { if( 0!=rc ){ final String msg = sqlite3_errmsg(db); sqlite3_close(db); - Util.toss(TestFailure.class, "db open failed with code ", + Util.toss(SQLTesterException.class, "db open failed with code ", rc," and message: ",msg); } return aDb[iCurrentDb] = db; @@ -364,70 +333,89 @@ public class SQLTester { final StringBuilder sb = (ResultBufferMode.NONE==appendMode) ? null : resultBuffer; //outln("sqlChunk len= = ",sqlChunk.length); - while(pos < sqlChunk.length){ - if(pos > 0){ - sqlChunk = Arrays.copyOfRange(sqlChunk, pos, - sqlChunk.length); - } - if( 0==sqlChunk.length ) break; - rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); - /*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ", - new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ - if( 0!=rc ){ - if(throwOnError){ - Util.toss(RuntimeException.class, "db op failed with rc=" - +rc+": "+sqlite3_errmsg(db)); - }else if( null!=sb ){ - appendDbErr(db, sb, rc); + try{ + while(pos < sqlChunk.length){ + if(pos > 0){ + sqlChunk = Arrays.copyOfRange(sqlChunk, pos, + sqlChunk.length); } - break; - } - pos = oTail.getValue(); - stmt = outStmt.getValue(); - if( null == stmt ){ - // empty statement was parsed. - continue; - } - if( null!=sb ){ - // Add the output to the result buffer... - final int nCol = sqlite3_column_count(stmt); - while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ - for(int i = 0; i < nCol; ++i){ - if( spacing++ > 0 ) sb.append(' '); - String val = sqlite3_column_text16(stmt, i); - if( null==val ){ - sb.append( nullView ); - continue; - } - switch(appendMode){ - case ASIS: - sb.append( val ); - break; - case ESCAPED: - sb.append( escapeSqlValue(val) ); - break; - default: - Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); - } - } - if( ResultRowMode.NEWLINE == lineMode ){ - spacing = 0; - sb.append('\n'); + if( 0==sqlChunk.length ) break; + rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); + /*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ", + new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ + if( 0!=rc ){ + if(throwOnError){ + Util.toss(RuntimeException.class, "db op failed with rc=" + +rc+": "+sqlite3_errmsg(db)); + }else if( null!=sb ){ + appendDbErr(db, sb, rc); } + break; + } + pos = oTail.getValue(); + stmt = outStmt.getValue(); + if( null == stmt ){ + // empty statement was parsed. + continue; } - }else{ - while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} - } - sqlite3_finalize(stmt); - if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; - else if( rc!=0 ){ if( null!=sb ){ - appendDbErr(db, sb, rc); + // Add the output to the result buffer... + final int nCol = sqlite3_column_count(stmt); + String colName = null, val = null; + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){ + for(int i = 0; i < nCol; ++i){ + if( spacing++ > 0 ) sb.append(' '); + if( emitColNames ){ + colName = sqlite3_column_name(stmt, i); + switch(appendMode){ + case ASIS: + sb.append( colName ); + break; + case ESCAPED: + sb.append( escapeSqlValue(colName) ); + break; + default: + Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + } + sb.append(' '); + } + val = sqlite3_column_text16(stmt, i); + if( null==val ){ + sb.append( nullView ); + continue; + } + switch(appendMode){ + case ASIS: + sb.append( val ); + break; + case ESCAPED: + sb.append( escapeSqlValue(val) ); + break; + default: + Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + } + } + if( ResultRowMode.NEWLINE == lineMode ){ + spacing = 0; + sb.append('\n'); + } + } + }else{ + while( SQLITE_ROW == (rc = sqlite3_step(stmt)) ){} + } + sqlite3_finalize(stmt); + stmt = null; + if(SQLITE_ROW==rc || SQLITE_DONE==rc) rc = 0; + else if( rc!=0 ){ + if( null!=sb ){ + appendDbErr(db, sb, rc); + } + break; } - break; } + }finally{ + sqlite3_finalize(stmt); } - sqlite3_finalize(stmt); if( 0!=rc && throwOnError ){ Util.toss(RuntimeException.class, "db op failed with rc=" +rc+": "+sqlite3_errmsg(db)); @@ -443,8 +431,6 @@ public class SQLTester { final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ t.setVerbosity(t.getVerbosity() + 1); - }else if( flag.equals("2") ){ - v2 = true; }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } @@ -452,11 +438,13 @@ public class SQLTester { } t.addTestScript(a); } - if( v2 ) t.runTests2(); - else t.runTests(); - t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); - if( t.nAbortedScript > 0 ){ - t.outln("Aborted ",t.nAbortedScript," script(s)."); + try { + t.runTests(); + }finally{ + t.outln("Processed ",t.nTotalTest," test(s) in ",t.nTestFile," file(s)."); + if( t.nAbortedScript > 0 ){ + t.outln("Aborted ",t.nAbortedScript," script(s)."); + } } } @@ -492,363 +480,6 @@ public class SQLTester { } -/** - Base class for test script commands. It provides a set of utility - APIs for concrete command implementations. - - Each subclass must have a public no-arg ctor and must implement - the process() method which is abstract in this class. - - Commands are intended to be stateless, except perhaps for counters - and similar internals. No state which changes the behavior between - any two invocations of process() should be retained. -*/ -abstract class Command { - protected Command(){} - - /** - Must process one command-unit of work and either return - (on success) or throw (on error). - - The first argument is the context of the test. - - argv is a list with the command name followed by any arguments to - that command. The argcCheck() method from this class provides - very basic argc validation. - - The content is any text content which was specified after the - command, or null if there is null. Any command which does not - permit content must pass that argument to affirmNoContent() in - their constructor (or perform an equivalent check). Similary, - those which require content must pass it to affirmHasContent() - (or equivalent). - */ - public abstract void process(SQLTester tester, String[] argv, String content) throws Exception; - - /** - If argv.length-1 (-1 because the command's name is in argv[0]) does not - fall in the inclusive range (min,max) then this function throws. Use - a max value of -1 to mean unlimited. - */ - protected final void argcCheck(String[] argv, int min, int max) throws Exception{ - int argc = argv.length-1; - if(argc=0 && argc>max)){ - if( min==max ){ - Util.badArg(argv[0]," requires exactly ",min," argument(s)"); - }else if(max>0){ - Util.badArg(argv[0]," requires ",min,"-",max," arguments."); - }else{ - Util.badArg(argv[0]," requires at least ",min," arguments."); - } - } - } - - /** - Equivalent to argcCheck(argv,argc,argc). - */ - protected final void argcCheck(String[] argv, int argc) throws Exception{ - argcCheck(argv, argc, argc); - } - - //! Throws if content is not null. - protected void affirmNoContent(String content) throws Exception{ - if(null != content){ - Util.badArg(this.getClass().getName()," does not accept content ", - "but got:\n",content); - } - } - - //! Throws if content is null. - protected void affirmHasContent(String content) throws Exception{ - if(null == content){ - Util.badArg(this.getClass().getName()," requires content."); - } - } -} - -class CloseDbCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0,1); - affirmNoContent(content); - Integer id; - if(argv.length>1){ - String arg = argv[1]; - if("all".equals(arg)){ - //t.verbose(argv[0]," all dbs"); - t.closeAllDbs(); - return; - } - else{ - id = Integer.parseInt(arg); - } - }else{ - id = t.getCurrentDbId(); - } - t.closeDb(id); - t.verbose(argv[0]," db ",id); - } -} - -//! --db command -class DbCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - final sqlite3 db = t.setCurrentDb( Integer.parseInt(argv[1]) ); - //t.verbose(argv[0]," set db to ",db); - } -} - -//! --glob command -class GlobCommand extends Command { - private boolean negate = false; - public GlobCommand(){} - protected GlobCommand(boolean negate){ this.negate = negate; } - - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, - ResultRowMode.ONELINE, sql); - final String result = t.getResultText(); - final String sArgs = Util.argvToString(argv); - //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = argv[1]; - rc = SQLTester.strglob(glob, result); - if( (negate && 0==rc) || (!negate && 0!=rc) ){ - Util.toss(TestFailure.class, argv[0], " mismatch: ", - glob," vs input: ",result); - } - } -} - -//! --json command -class JsonCommand extends ResultCommand { - public JsonCommand(){ super(ResultBufferMode.ASIS); } -} - -//! --json-block command -class JsonBlockCommand extends TableResultCommand { - public JsonBlockCommand(){ super(true); } -} - -//! --new command -class NewDbCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - String fname = argv[1]; - Util.unlink(fname); - final sqlite3 db = t.openDb(fname, true); - //t.verbose(argv[0]," db ",db); - } -} - -//! Placeholder dummy/no-op commands -class NoopCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - } -} - -//! --notglob command -class NotGlobCommand extends GlobCommand { - public NotGlobCommand(){ - super(true); - } -} - -//! --null command -class NullCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - t.setNullValue(argv[1]); - //t.verbose(argv[0]," ",argv[1]); - } -} - -//! --open command -class OpenDbCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmNoContent(content); - String fname = argv[1]; - final sqlite3 db = t.openDb(fname, false); - //t.verbose(argv[0]," db ",db); - } -} - -//! --print command -class PrintCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - if( 1==argv.length && null==content ){ - Util.badArg(argv[0]," requires at least 1 argument or body content."); - } - if( argv.length > 1 ) t.outln("\t",Util.argvToString(argv)); - if( null!=content ) t.outln(content.replaceAll("(?m)^", "\t")); - } -} - -//! --result command -class ResultCommand extends Command { - private final ResultBufferMode bufferMode; - protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } - public ResultCommand(){ this(ResultBufferMode.ESCAPED); } - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0,-1); - affirmNoContent(content); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); - final String result = t.getResultText().trim(); - final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - if( !result.equals(sArgs) ){ - t.outln(argv[0]," FAILED comparison. Result buffer:\n", - result,"\nargs:\n",sArgs); - Util.toss(TestFailure.class, argv[0]," comparison failed."); - } - } -} - -//! --run command -class RunCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0,1); - affirmHasContent(content); - final sqlite3 db = (1==argv.length) - ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - int rc = t.execSql(db, false, ResultBufferMode.NONE, - ResultRowMode.ONELINE, content); - if( 0!=rc && t.isVerbose() ){ - String msg = sqlite3_errmsg(db); - t.verbose(argv[0]," non-fatal command error #",rc,": ", - msg,"\nfor SQL:\n",content); - } - } -} - -//! --tableresult command -class TableResultCommand extends Command { - private final boolean jsonMode; - protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } - public TableResultCommand(){ this(false); } - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,0); - affirmHasContent(content); - t.incrementTestCounter(); - if( !content.endsWith("\n--end") ){ - Util.toss(TestFailure.class, argv[0], " must be terminated with --end."); - }else{ - int n = content.length(); - content = content.substring(0, n-6); - } - final String[] globs = content.split("\\s*\\n\\s*"); - if( globs.length < 1 ){ - Util.toss(TestFailure.class, argv[0], " requires 1 or more ", - (jsonMode ? "json snippets" : "globs"),"."); - } - final String sql = t.takeInputBuffer(); - t.execSql(null, true, - jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, - ResultRowMode.NEWLINE, sql); - final String rbuf = t.getResultText(); - final String[] res = rbuf.split("\n"); - if( res.length != globs.length ){ - Util.toss(TestFailure.class, argv[0], " failure: input has ", - res.length," row(s) but expecting ",globs.length); - } - for(int i = 0; i < res.length; ++i){ - final String glob = globs[i].replaceAll("\\s+"," ").trim(); - //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); - if( jsonMode ){ - if( !glob.equals(res[i]) ){ - Util.toss(TestFailure.class, argv[0], " json <<",glob, - ">> does not match: <<",res[i],">>"); - } - }else if( 0 != SQLTester.strglob(glob, res[i]) ){ - Util.toss(TestFailure.class, argv[0], " glob <<",glob, - ">> does not match: <<",res[i],">>"); - } - } - } -} - -//! --testcase command -class TestCaseCommand extends Command { - public void process(SQLTester t, String[] argv, String content) throws Exception{ - argcCheck(argv,1); - affirmHasContent(content); - // TODO: do something with the test name - t.clearResultBuffer(); - t.clearInputBuffer().append(content); - //t.verbose(argv[0]," input buffer: ",content); - } -} - -/** - Helper for dispatching Command instances. -*/ -class CommandDispatcher { - - private static java.util.Map commandMap = - new java.util.HashMap<>(); - - /** - Returns a (cached) instance mapped to name, or null if no match - is found. - */ - static Command getCommandByName(String name){ - Command rv = commandMap.get(name); - if( null!=rv ) return rv; - switch(name){ - case "close": rv = new CloseDbCommand(); break; - case "db": rv = new DbCommand(); break; - case "glob": rv = new GlobCommand(); break; - case "json": rv = new JsonCommand(); break; - case "json-block": rv = new JsonBlockCommand(); break; - case "new": rv = new NewDbCommand(); break; - case "notglob": rv = new NotGlobCommand(); break; - case "null": rv = new NullCommand(); break; - case "oom": rv = new NoopCommand(); break; - case "open": rv = new OpenDbCommand(); break; - case "print": rv = new PrintCommand(); break; - case "result": rv = new ResultCommand(); break; - case "run": rv = new RunCommand(); break; - case "tableresult": rv = new TableResultCommand(); break; - case "testcase": rv = new TestCaseCommand(); break; - default: rv = null; break; - } - if( null!=rv ) commandMap.put(name, rv); - return rv; - } - - /** - Treats argv[0] as a command name, looks it up with - getCommandByName(), and calls process() on that instance, passing - it arguments given to this function. - */ - static void dispatch(SQLTester tester, String[] argv, String content) throws Exception{ - final Command cmd = getCommandByName(argv[0]); - if(null == cmd){ - final TestScript ts = tester.getCurrentScript(); - if( tester.skipUnknownCommands() ){ - tester.outln("WARNING: skipping remainder of [",ts.getModuleName(), - "] because it contains unknown command '",argv[0],"'."); - throw new SkipTestRemainder(ts); - } - Util.toss(IllegalArgumentException.class, - "No command handler found for '"+argv[0]+"' in ", - ts.getName()); - } - //tester.verbose("Running ",argv[0]," with:\n", content); - cmd.process(tester, argv, content); - } -} - /** General utilities for the SQLTester bits. */ diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 742cf4fa52..8f6863eef0 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -12,29 +12,384 @@ ** This file contains the TestScript part of the SQLTester framework. */ package org.sqlite.jni.tester; -import java.util.List; -import java.util.ArrayList; -import java.io.*; +import static org.sqlite.jni.SQLite3Jni.*; +import org.sqlite.jni.sqlite3; +import java.util.Arrays; +import java.nio.charset.StandardCharsets; import java.util.regex.*; +class TestScriptFailed extends SQLTesterException { + public TestScriptFailed(TestScript ts, String msg){ + super(ts.getOutputPrefix()+": "+msg); + } +} + +class UnknownCommand extends SQLTesterException { + public UnknownCommand(TestScript ts, String cmd){ + super(ts.getOutputPrefix()+": unknown command: "+cmd); + } +} + +class IncompatibleDirective extends SQLTesterException { + public IncompatibleDirective(TestScript ts, String line){ + super(ts.getOutputPrefix()+": incompatible directive: "+line); + } +} + +/** + Base class for test script commands. It provides a set of utility + APIs for concrete command implementations. + + Each subclass must have a public no-arg ctor and must implement + the process() method which is abstract in this class. + + Commands are intended to be stateless, except perhaps for counters + and similar internals. Specifically, no state which changes the + behavior between any two invocations of process() should be + retained. +*/ +abstract class Command { + protected Command(){} + + /** + Must process one command-unit of work and either return + (on success) or throw (on error). + + The first two arguments specify the context of the test. + + argv is a list with the command name followed by any arguments to + that command. The argcCheck() method from this class provides + very basic argc validation. + */ + public abstract void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception; + + /** + If argv.length-1 (-1 because the command's name is in argv[0]) does not + fall in the inclusive range (min,max) then this function throws. Use + a max value of -1 to mean unlimited. + */ + protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{ + int argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + ts.toss(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + ts.toss(argv[0]," requires ",min,"-",max," arguments."); + }else{ + ts.toss(argv[0]," requires at least ",min," arguments."); + } + } + } + + /** + Equivalent to argcCheck(argv,argc,argc). + */ + protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ + argcCheck(ts, argv, argc, argc); + } +} + +//! --close command +class CloseDbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + } +} + +//! --column-names command +class ColumnNamesCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); + } +} + +//! --db command +class DbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.setCurrentDb( Integer.parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand extends Command { + private boolean negate = false; + public GlobCommand(){} + protected GlobCommand(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText(); + final String sArgs = Util.argvToString(argv); + //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + final String glob = argv[1]; + rc = SQLTester.strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --json command +class JsonCommand extends ResultCommand { + public JsonCommand(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + public JsonBlockCommand(){ super(true); } +} + +//! --new command +class NewDbCommand extends OpenDbCommand { + public NewDbCommand(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + } +} + +//! --notglob command +class NotGlobCommand extends GlobCommand { + public NotGlobCommand(){ + super(true); + } +} + +//! --null command +class NullCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.setNullValue( argv[1] ); + } +} + +//! --open command +class OpenDbCommand extends Command { + private boolean createIfNeeded = false; + public OpenDbCommand(){} + protected OpenDbCommand(boolean c){createIfNeeded = c;} + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.openDb(argv[1], createIfNeeded); + } +} + +//! --print command +class PrintCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + st.out(ts.getOutputPrefix(),": "); + final String body = ts.fetchCommandBody(); + if( 1==argv.length && null==body ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + if( null!=body ){ + st.out(body); + } + } +} + +//! --result command +class ResultCommand extends Command { + private final ResultBufferMode bufferMode; + protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nargs:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + t.verbose(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand extends Command { + private final boolean jsonMode; + protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand(){ this(false); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + // TODO?: do something with the test name + t.clearResultBuffer(); + t.clearInputBuffer(); + } +} + +class CommandDispatcher2 { + + private static java.util.Map commandMap = + new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ + static Command getCommandByName(String name){ + Command rv = commandMap.get(name); + if( null!=rv ) return rv; + switch(name){ + case "close": rv = new CloseDbCommand(); break; + case "column-names":rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + default: rv = null; break; + } + if( null!=rv ) commandMap.put(name, rv); + return rv; + } + + /** + Treats argv[0] as a command name, looks it up with + getCommandByName(), and calls process() on that instance, passing + it arguments given to this function. + */ + static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{ + final Command cmd = getCommandByName(argv[0]); + if(null == cmd){ + throw new UnknownCommand(ts, argv[0]); + } + cmd.process(tester, ts, argv); + } +} + + /** This class represents a single test script. It handles (or delegates) its the reading-in and parsing, but the details of evaluation are delegated elsewhere. */ class TestScript { - private String name = null; + private String filename = null; private String moduleName = null; - private List chunks = null; + private final Cursor cur = new Cursor(); private final Outer outer = new Outer(); - private String ignoreReason = null; - private byte[] baScript = null; - /* One "chunk" of input, representing a single command and - its optional body content. */ - private static final class CommandChunk { - public String[] argv = null; - public String content = null; + private static final class Cursor { + private final StringBuilder sb = new StringBuilder(); + byte[] src = null; + int pos = 0; + int putbackPos = 0; + int putbackLineNo = 0; + int lineNo = 0 /* yes, zero */; + int peekedPos = 0; + int peekedLineNo = 0; + boolean inComment = false; + + void reset(){ + sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false; + } } private byte[] readFile(String filename) throws Exception { @@ -43,229 +398,289 @@ class TestScript { /** Initializes the script with the content of the given file. - Throws if it cannot read the file or if tokenizing it fails. + Throws if it cannot read the file. */ public TestScript(String filename) throws Exception{ - name = filename; - baScript = readFile(filename); - setContent(new String( - baScript, java.nio.charset.StandardCharsets.UTF_8 - )); + this.filename = filename; + setVerbosity(2); + cur.src = readFile(filename); } - /** - Initializes the script with the given content, copied at - construction-time. The first argument is a filename for that - content. It need not refer to a real file - it's for display - purposes only. - */ - public TestScript(String virtualName, StringBuffer content) - throws RuntimeException { - name = virtualName; - setContent(content.toString()); - } - - private void setContent(String c){ - this.chunks = chunkContent(c); - } - - public String getName(){ - return name; + public String getFilename(){ + return filename; } public String getModuleName(){ return moduleName; } - public boolean isIgnored(){ - return null!=ignoreReason; - } - - public String getIgnoredReason(){ - return ignoreReason; - } - public void setVerbosity(int level){ outer.setVerbosity(level); } + public String getOutputPrefix(){ + return "["+(moduleName==null ? filename : moduleName)+"] line "+ + cur.lineNo; + } @SuppressWarnings("unchecked") - private TestScript verbose(Object... vals){ - outer.verbose(vals); + private TestScript verboseN(int level, Object... vals){ + final int verbosity = outer.getVerbosity(); + if(verbosity>=level){ + outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), + getOutputPrefix(),": "); + outer.outln(vals); + } return this; } - private static final Pattern patternHashLine = - Pattern.compile("^#", Pattern.MULTILINE); + private TestScript verbose1(Object... vals){return verboseN(1,vals);} + private TestScript verbose2(Object... vals){return verboseN(2,vals);} + + @SuppressWarnings("unchecked") + public TestScript warn(Object... vals){ + outer.out("WARNING ", getOutputPrefix(),": "); + outer.outln(vals); + return this; + } + + private void reset(){ + cur.reset(); + } + + + /** + Returns the next line from the buffer, minus the trailing EOL. + + Returns null when all input is consumed. Throws if it reads + illegally-encoded input, e.g. (non-)characters in the range + 128-256. + */ + String getLine(){ + if( cur.pos==cur.src.length ){ + return null /* EOF */; + } + cur.putbackPos = cur.pos; + cur.putbackLineNo = cur.lineNo; + cur.sb.setLength(0); + final boolean skipLeadingWs = false; + byte b = 0, prevB = 0; + int i = cur.pos; + if(skipLeadingWs) { + /* Skip any leading spaces, including newlines. This will eliminate + blank lines. */ + for(; i < cur.src.length; ++i, prevB=b){ + b = cur.src[i]; + switch((int)b){ + case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; + case 10/*NL*/: ++cur.lineNo; continue; + default: break; + } + break; + } + if( i==cur.src.length ){ + return null /* EOF */; + } + } + boolean doBreak = false; + final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; + int nChar = 0 /* number of bytes in the char */; + for(; i < cur.src.length && !doBreak; ++i){ + b = cur.src[i]; + switch( (int)b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++cur.lineNo; + if(cur.sb.length()>0) doBreak = true; + // Else it's an empty string + break; + default: + /* Multi-byte chars need to be gathered up and appended at + one time. Appending individual bytes to the StringBuffer + appends their integer value. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); + break; + } + if( 1==nChar ){ + cur.sb.append((char)b); + }else{ + for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; + cur.sb.append(new String(Arrays.copyOf(aChar, nChar), + StandardCharsets.UTF_8)); + i += nChar-1; + } + break; + } + } + cur.pos = i; + final String rv = cur.sb.toString(); + if( i==cur.src.length && 0==rv.length() ){ + return null /* EOF */; + } + return rv; + }/*getLine()*/ + + /** + Fetches the next line then resets the cursor to its pre-call + state. consumePeeked() can be used to consume this peeked line + without having to re-parse it. + */ + String peekLine(){ + final int oldPos = cur.pos; + final int oldPB = cur.putbackPos; + final int oldPBL = cur.putbackLineNo; + final int oldLine = cur.lineNo; + final String rc = getLine(); + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + return rc; + } + + /** + Only valid after calling peekLine() and before calling getLine(). + This places the cursor to the position it would have been at had + the peekLine() had been fetched with getLine(). + */ + void consumePeeked(){ + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + void putbackLine(){ + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + private static final Pattern patternRequiredProperties = - Pattern.compile("REQUIRED_PROPERTIES:[ \\t]*(\\S+\\s*)\\n"); - /** - Returns true if the given script content should be ignored - (because it contains certain content which indicates such). - */ - private boolean shouldBeIgnored(String content){ - if( null == moduleName ){ - ignoreReason = "No module name."; - return true; - }else if( content.indexOf("\n|")>=0 ){ - ignoreReason = "Contains newline-pipe combination."; - return true; - }else if( content.indexOf(" MODULE_NAME:")>=0 ){ - ignoreReason = "Contains MODULE_NAME."; - return true; - }else if( content.indexOf("MIXED_MODULE_NAME:")>=0 ){ - ignoreReason = "Contains MIXED_MODULE_NAME."; - return true; - } - Matcher m = patternHashLine.matcher(content); - if( m.find() ){ - ignoreReason = "C-preprocessor line found."; - return true; - } - m = patternRequiredProperties.matcher(content); - if( m.find() ){ - ignoreReason = "REQUIRED_PROPERTIES found: "+m.group(1).trim(); - return true; - } - return false; - } - - private boolean findModuleName(String content){ - final Pattern p = Pattern.compile( - "SCRIPT_MODULE_NAME:\\s+(\\S+)\\s*\n", - Pattern.MULTILINE - ); - final Matcher m = p.matcher(content); - moduleName = m.find() ? m.group(1) : null; - return moduleName != null; - } + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); + private static final Pattern patternScriptModuleName = + Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternMixedModuleName = + Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternCommand = + Pattern.compile("^--(([a-z-]+)( .*)?)$"); /** - Chop script up into chunks containing individual commands and - their inputs. The approach taken here is not as robust as - line-by-line parsing would be but the framework is structured - such that we could replace this part without unduly affecting the - evaluation bits. The potential problems with this approach - include: - - - It's potentially possible that it will strip content out of a - testcase block. - - - It loses all file location information, so we can't report line - numbers of errors. - - If/when that becomes a problem, it can be refactored. + Looks for "directives." If a compatible one is found, it is + processed and this function returns. If an incompatible one is found, + a description of it is returned and processing of the test must + end immediately. */ - private List chunkContent(String content){ - findModuleName(content); - if( shouldBeIgnored(content) ){ - chunks = null; - return null; + private void checkForDirective(String line) throws IncompatibleDirective { + if(line.startsWith("#")){ + throw new IncompatibleDirective(this, "C-preprocessor input: "+line); + }else if(line.startsWith("---")){ + new IncompatibleDirective(this, "triple-dash: "+line); } + Matcher m = patternScriptModuleName.matcher(line); + if( m.find() ){ + moduleName = m.group(1); + return; + } + m = patternRequiredProperties.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); + } + m = patternMixedModuleName.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); + } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } + return; + } - // First, strip out any content which we know we can ignore... - final String sCComment = "[/][*]([*](?![/])|[^*])*[*][/]"; - final String s3Dash = "^---+[^\\n]*\\n"; - final String sEmptyLine = "^\\n"; - final String sOom = "^--oom\\n" - /* Workaround: --oom is a top-level command in some contexts - and appears in --testcase blocks in others. We don't - do anything with --oom commands aside from ignore them, so - elide them all to fix the --testcase blocks which contain - them. */; - final List lPats = new ArrayList<>(); - lPats.add(sCComment); - lPats.add(s3Dash); - lPats.add(sEmptyLine); - lPats.add(sOom); - //verbose("Content:").verbose(content).verbose(""); - for( String s : lPats ){ - final Pattern p = Pattern.compile( - s, Pattern.MULTILINE - ); - final Matcher m = p.matcher(content); - /*verbose("Pattern {{{ ",p.pattern()," }}} with flags ", - p.flags()," matches:" - );*/ - int n = 0; - //while( m.find() ) verbose("#",(++n),"\t",m.group(0).trim()); - content = m.replaceAll(""); - } - // Chunk the newly-cleaned text into individual commands and their input... - // First split up the input into command-size blocks... - final List blocks = new ArrayList<>(); - final Pattern p = Pattern.compile( - "^--(?!end)[a-z]+", Pattern.MULTILINE - // --end is a marker used by --tableresult and --(not)glob. - ); - final Matcher m = p.matcher(content); - int ndxPrev = 0, pos = 0, i = 0; - //verbose("Trimmed content:").verbose(content).verbose(""); - while( m.find() ){ - pos = m.start(); - final String block = content.substring(ndxPrev, pos).trim(); - if( 0==ndxPrev && pos>ndxPrev ){ - /* Initial block of non-command state. Skip it. */ - ndxPrev = pos + 2; - continue; - } - if( !block.isEmpty() ){ - ++i; - //verbose("BLOCK #",i," ",+ndxPrev,"..",pos,block); - blocks.add( block ); - } - ndxPrev = pos + 2; - } - if( ndxPrev < content.length() ){ - // This all belongs to the final command - final String block = content.substring(ndxPrev, content.length()).trim(); - if( !block.isEmpty() ){ - ++i; - //verbose("BLOCK #",(++i)," ",block); - blocks.add( block ); - } - } - // Next, convert those blocks into higher-level CommandChunks... - final List rc = new ArrayList<>(); - for( String block : blocks ){ - final CommandChunk chunk = new CommandChunk(); - final String[] parts = block.split("\\n", 2); - chunk.argv = parts[0].split("\\s+"); - if( parts.length>1 && parts[1].length()>0 ){ - chunk.content = parts[1] - /* reminder: don't trim() here. It would be easier - for Command impls if we did but it makes debug - output look weird. */; - } - rc.add( chunk ); + boolean isCommandLine(String line, boolean checkForImpl){ + final Matcher m = patternCommand.matcher(line); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); } return rc; } /** - Runs this test script in the context of the given tester object. + If line looks like a command, returns an argv for that command + invocation, else returns null. */ - public void run(SQLTester tester) throws Exception { - final int verbosity = tester.getVerbosity(); - if( null==chunks ){ - outer.outln("This test contains content which forces it to be skipped."); - }else{ - int n = 0; - for(CommandChunk chunk : chunks){ - if(verbosity>0){ - outer.out("VERBOSE",(verbosity>1 ? "+ " : " "),moduleName, - " #",++n," ",chunk.argv[0], - " ",Util.argvToString(chunk.argv)); - if(verbosity>1 && null!=chunk.content){ - outer.out("\n", chunk.content); - } - outer.out("\n"); - } - CommandDispatcher.dispatch( - tester, chunk.argv, - (null==chunk.content) ? null : chunk.content.trim() - ); + String[] getCommandArgv(String line){ + final Matcher m = patternCommand.matcher(line); + return m.find() ? m.group(1).trim().split("\\s+") : null; + } + + /** + Fetches lines until the next recognized command. Throws if + checkForDirective() does. Returns null if there is no input or + it's only whitespace. The returned string retains all whitespace. + + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. + */ + String fetchCommandBody(){ + final StringBuilder sb = new StringBuilder(); + String line; + while( (null != (line = peekLine())) ){ + checkForDirective(line); + if( !isCommandLine(line, true) ){ + sb.append(line).append("\n"); + consumePeeked(); + }else{ + break; } } + line = sb.toString(); + return line.trim().isEmpty() ? null : line; + } + + private void processCommand(SQLTester t, String[] argv) throws Exception{ + verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); + if(outer.getVerbosity()>1){ + final String input = t.getInputText(); + if( !input.isEmpty() ) verbose2("Input buffer = ",input); + } + CommandDispatcher2.dispatch(t, this, argv); + } + + void toss(Object... msg) throws TestScriptFailed { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + throw new TestScriptFailed(this, sb.toString()); + } + + /** + Runs this test script in the context of the given tester object. + */ + @SuppressWarnings("unchecked") + public boolean run(SQLTester tester) throws Exception { + reset(); + setVerbosity(tester.getVerbosity()); + String line, directive; + String[] argv; + while( null != (line = getLine()) ){ + //verbose(line); + checkForDirective(line); + argv = getCommandArgv(line); + if( null!=argv ){ + processCommand(tester, argv); + continue; + } + tester.appendInput(line,true); + } + return true; } } diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java b/ext/jni/src/org/sqlite/jni/tester/TestScript2.java deleted file mode 100644 index ee85b72bd8..0000000000 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript2.java +++ /dev/null @@ -1,680 +0,0 @@ -/* -** 2023-08-08 -** -** 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 the TestScript2 part of the SQLTester framework. -*/ -package org.sqlite.jni.tester; -import static org.sqlite.jni.SQLite3Jni.*; -import org.sqlite.jni.sqlite3; -import java.util.Arrays; -import java.nio.charset.StandardCharsets; -import java.util.regex.*; - -class SQLTestException extends RuntimeException { - public SQLTestException(String msg){ - super(msg); - } -} - -class TestScript2Failed extends SQLTestException { - public TestScript2Failed(TestScript2 ts, String msg){ - super(ts.getOutputPrefix()+": "+msg); - } -} - -class SkipTestRemainder2 extends SQLTestException { - public SkipTestRemainder2(TestScript2 ts){ - super(ts.getOutputPrefix()+": skipping remainder"); - } -} - -class IncompatibleDirective extends SQLTestException { - public IncompatibleDirective(TestScript2 ts, String line){ - super(ts.getOutputPrefix()+": incompatible directive: "+line); - } -} - -class UnknownCommand extends SQLTestException { - public UnknownCommand(TestScript2 ts, String line){ - super(ts.getOutputPrefix()+": unknown command: "+line); - } -} - -abstract class Command2 { - protected Command2(){} - - public abstract void process( - SQLTester st, TestScript2 ts, String[] argv - ) throws Exception; - - /** - If argv.length-1 (-1 because the command's name is in argv[0]) does not - fall in the inclusive range (min,max) then this function throws. Use - a max value of -1 to mean unlimited. - */ - protected final void argcCheck(String[] argv, int min, int max) throws Exception{ - int argc = argv.length-1; - if(argc=0 && argc>max)){ - if( min==max ){ - Util.badArg(argv[0]," requires exactly ",min," argument(s)"); - }else if(max>0){ - Util.badArg(argv[0]," requires ",min,"-",max," arguments."); - }else{ - Util.badArg(argv[0]," requires at least ",min," arguments."); - } - } - } - - /** - Equivalent to argcCheck(argv,argc,argc). - */ - protected final void argcCheck(String[] argv, int argc) throws Exception{ - argcCheck(argv, argc, argc); - } -} - -//! --close command -class CloseDbCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,0,1); - Integer id; - if(argv.length>1){ - String arg = argv[1]; - if("all".equals(arg)){ - t.closeAllDbs(); - return; - } - else{ - id = Integer.parseInt(arg); - } - }else{ - id = t.getCurrentDbId(); - } - t.closeDb(id); - } -} - -//! --db command -class DbCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - t.setCurrentDb( Integer.parseInt(argv[1]) ); - } -} - -//! --glob command -class GlobCommand2 extends Command2 { - private boolean negate = false; - public GlobCommand2(){} - protected GlobCommand2(boolean negate){ this.negate = negate; } - - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, - ResultRowMode.ONELINE, sql); - final String result = t.getResultText(); - final String sArgs = Util.argvToString(argv); - //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = argv[1]; - rc = SQLTester.strglob(glob, result); - if( (negate && 0==rc) || (!negate && 0!=rc) ){ - ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); - } - } -} - -//! --json command -class JsonCommand2 extends ResultCommand2 { - public JsonCommand2(){ super(ResultBufferMode.ASIS); } -} - -//! --json-block command -class JsonBlockCommand2 extends TableResultCommand2 { - public JsonBlockCommand2(){ super(true); } -} - -//! --new command -class NewDbCommand2 extends OpenDbCommand2 { - public NewDbCommand2(){ super(true); } -} - -//! Placeholder dummy/no-op commands -class NoopCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - } -} - -//! --notglob command -class NotGlobCommand2 extends GlobCommand2 { - public NotGlobCommand2(){ - super(true); - } -} - -//! --null command -class NullCommand2 extends Command2 { - public void process( - SQLTester st, TestScript2 ts, String[] argv - ) throws Exception{ - argcCheck(argv,1); - st.setNullValue( argv[1] ); - } -} - -//! --open command -class OpenDbCommand2 extends Command2 { - private boolean createIfNeeded = false; - public OpenDbCommand2(){} - protected OpenDbCommand2(boolean c){createIfNeeded = c;} - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - t.openDb(argv[1], createIfNeeded); - } -} - -//! --print command -class PrintCommand2 extends Command2 { - public void process( - SQLTester st, TestScript2 ts, String[] argv - ) throws Exception{ - st.out(ts.getOutputPrefix(),": "); - final String body = ts.fetchCommandBody(); - if( 1==argv.length && null==body ){ - st.out( st.getInputText() ); - }else{ - st.outln( Util.argvToString(argv) ); - } - if( null!=body ){ - st.out(body); - } - } -} - -//! --result command -class ResultCommand2 extends Command2 { - private final ResultBufferMode bufferMode; - protected ResultCommand2(ResultBufferMode bm){ bufferMode = bm; } - public ResultCommand2(){ this(ResultBufferMode.ESCAPED); } - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,0,-1); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); - final String result = t.getResultText().trim(); - final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - if( !result.equals(sArgs) ){ - t.outln(argv[0]," FAILED comparison. Result buffer:\n", - result,"\nargs:\n",sArgs); - ts.toss(argv[0]+" comparison failed."); - } - } -} - -//! --run command -class RunCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,0,1); - final sqlite3 db = (1==argv.length) - ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - final String sql = t.takeInputBuffer(); - int rc = t.execSql(db, false, ResultBufferMode.NONE, - ResultRowMode.ONELINE, sql); - if( 0!=rc && t.isVerbose() ){ - String msg = sqlite3_errmsg(db); - t.verbose(argv[0]," non-fatal command error #",rc,": ", - msg,"\nfor SQL:\n",sql); - } - } -} - -//! --tableresult command -class TableResultCommand2 extends Command2 { - private final boolean jsonMode; - protected TableResultCommand2(boolean jsonMode){ this.jsonMode = jsonMode; } - public TableResultCommand2(){ this(false); } - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,0); - t.incrementTestCounter(); - String body = ts.fetchCommandBody(); - if( null==body ) ts.toss("Missing ",argv[0]," body."); - body = body.trim(); - if( !body.endsWith("\n--end") ){ - ts.toss(argv[0], " must be terminated with --end."); - }else{ - int n = body.length(); - body = body.substring(0, n-6); - } - final String[] globs = body.split("\\s*\\n\\s*"); - if( globs.length < 1 ){ - ts.toss(argv[0], " requires 1 or more ", - (jsonMode ? "json snippets" : "globs"),"."); - } - final String sql = t.takeInputBuffer(); - t.execSql(null, true, - jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, - ResultRowMode.NEWLINE, sql); - final String rbuf = t.getResultText(); - final String[] res = rbuf.split("\n"); - if( res.length != globs.length ){ - ts.toss(argv[0], " failure: input has ", res.length, - " row(s) but expecting ",globs.length); - } - for(int i = 0; i < res.length; ++i){ - final String glob = globs[i].replaceAll("\\s+"," ").trim(); - //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); - if( jsonMode ){ - if( !glob.equals(res[i]) ){ - ts.toss(argv[0], " json <<",glob, ">> does not match: <<", - res[i],">>"); - } - }else if( 0 != SQLTester.strglob(glob, res[i]) ){ - ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); - } - } - } -} - -//! --testcase command -class TestCaseCommand2 extends Command2 { - public void process(SQLTester t, TestScript2 ts, String[] argv) throws Exception{ - argcCheck(argv,1); - // TODO?: do something with the test name - final String body = ts.fetchCommandBody(); - t.clearResultBuffer(); - t.clearInputBuffer().append(null==body ? "" : body); - } -} - -class CommandDispatcher2 { - - private static java.util.Map commandMap = - new java.util.HashMap<>(); - - /** - Returns a (cached) instance mapped to name, or null if no match - is found. - */ - static Command2 getCommandByName(String name){ - Command2 rv = commandMap.get(name); - if( null!=rv ) return rv; - switch(name){ - case "close": rv = new CloseDbCommand2(); break; - case "db": rv = new DbCommand2(); break; - case "glob": rv = new GlobCommand2(); break; - case "json": rv = new JsonCommand2(); break; - case "json-block": rv = new JsonBlockCommand2(); break; - case "new": rv = new NewDbCommand2(); break; - case "notglob": rv = new NotGlobCommand2(); break; - case "null": rv = new NullCommand2(); break; - case "oom": rv = new NoopCommand2(); break; - case "open": rv = new OpenDbCommand2(); break; - case "print": rv = new PrintCommand2(); break; - case "result": rv = new ResultCommand2(); break; - case "run": rv = new RunCommand2(); break; - case "tableresult": rv = new TableResultCommand2(); break; - case "testcase": rv = new TestCaseCommand2(); break; - default: rv = null; break; - } - if( null!=rv ) commandMap.put(name, rv); - return rv; - } - - /** - Treats argv[0] as a command name, looks it up with - getCommandByName(), and calls process() on that instance, passing - it arguments given to this function. - */ - static void dispatch(SQLTester tester, TestScript2 ts, String[] argv) throws Exception{ - final Command2 cmd = getCommandByName(argv[0]); - if(null == cmd){ - if( tester.skipUnknownCommands() ){ - ts.warn("skipping remainder because of unknown command '",argv[0],"'."); - throw new SkipTestRemainder2(ts); - } - Util.toss(IllegalArgumentException.class, - ts.getOutputPrefix()+": no command handler found for '"+argv[0]+"'."); - } - cmd.process(tester, ts, argv); - } -} - - -/** - This class represents a single test script. It handles (or - delegates) its the reading-in and parsing, but the details of - evaluation are delegated elsewhere. -*/ -class TestScript2 { - private String filename = null; - private String moduleName = null; - private final Cursor cur = new Cursor(); - private final Outer outer = new Outer(); - - private static final class Cursor { - private final StringBuilder sb = new StringBuilder(); - byte[] src = null; - int pos = 0; - int putbackPos = 0; - int putbackLineNo = 0; - int lineNo = 0 /* yes, zero */; - int peekedPos = 0; - int peekedLineNo = 0; - boolean inComment = false; - - void reset(){ - sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false; - } - } - - private byte[] readFile(String filename) throws Exception { - return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); - } - - /** - Initializes the script with the content of the given file. - Throws if it cannot read the file. - */ - public TestScript2(String filename) throws Exception{ - this.filename = filename; - setVerbosity(2); - cur.src = readFile(filename); - } - - public String getFilename(){ - return filename; - } - - public String getModuleName(){ - return moduleName; - } - - public void setVerbosity(int level){ - outer.setVerbosity(level); - } - - public String getOutputPrefix(){ - return "["+(moduleName==null ? filename : moduleName)+"] line "+ - cur.lineNo; - } - @SuppressWarnings("unchecked") - private TestScript2 verboseN(int level, Object... vals){ - final int verbosity = outer.getVerbosity(); - if(verbosity>=level){ - outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), - getOutputPrefix(),": "); - outer.outln(vals); - } - return this; - } - - private TestScript2 verbose1(Object... vals){return verboseN(1,vals);} - private TestScript2 verbose2(Object... vals){return verboseN(2,vals);} - - @SuppressWarnings("unchecked") - public TestScript2 warn(Object... vals){ - outer.out("WARNING ", getOutputPrefix(),": "); - outer.outln(vals); - return this; - } - - @SuppressWarnings("unchecked") - private void tossSyntax(Object... msg){ - StringBuilder sb = new StringBuilder(); - sb.append(this.filename).append(":").append(cur.lineNo). - append(": "); - for(Object o : msg) sb.append(o); - throw new RuntimeException(sb.toString()); - } - - private void reset(){ - cur.reset(); - } - - - /** - Returns the next line from the buffer, minus the trailing EOL. - - Returns null when all input is consumed. Throws if it reads - illegally-encoded input, e.g. (non-)characters in the range - 128-256. - */ - String getLine(){ - if( cur.pos==cur.src.length ){ - return null /* EOF */; - } - cur.putbackPos = cur.pos; - cur.putbackLineNo = cur.lineNo; - cur.sb.setLength(0); - final boolean skipLeadingWs = false; - byte b = 0, prevB = 0; - int i = cur.pos; - if(skipLeadingWs) { - /* Skip any leading spaces, including newlines. This will eliminate - blank lines. */ - for(; i < cur.src.length; ++i, prevB=b){ - b = cur.src[i]; - switch((int)b){ - case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; - case 10/*NL*/: ++cur.lineNo; continue; - default: break; - } - break; - } - if( i==cur.src.length ){ - return null /* EOF */; - } - } - boolean doBreak = false; - final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; - int nChar = 0 /* number of bytes in the char */; - for(; i < cur.src.length && !doBreak; ++i){ - b = cur.src[i]; - switch( (int)b ){ - case 13/*CR*/: continue; - case 10/*NL*/: - ++cur.lineNo; - if(cur.sb.length()>0) doBreak = true; - // Else it's an empty string - break; - default: - /* Multi-byte chars need to be gathered up and appended at - one time. Appending individual bytes to the StringBuffer - appends their integer value. */ - nChar = 1; - switch( b & 0xF0 ){ - case 0xC0: nChar = 2; break; - case 0xE0: nChar = 3; break; - case 0xF0: nChar = 4; break; - default: - if( b > 127 ) tossSyntax("Invalid character (#"+(int)b+")."); - break; - } - if( 1==nChar ){ - cur.sb.append((char)b); - }else{ - for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; - cur.sb.append(new String(Arrays.copyOf(aChar, nChar), - StandardCharsets.UTF_8)); - i += nChar-1; - } - break; - } - } - cur.pos = i; - final String rv = cur.sb.toString(); - if( i==cur.src.length && 0==rv.length() ){ - return null /* EOF */; - } - return rv; - }/*getLine()*/ - - /** - Fetches the next line then resets the cursor to its pre-call - state. consumePeeked() can be used to consume this peeked line - without having to re-parse it. - */ - public String peekLine(){ - final int oldPos = cur.pos; - final int oldPB = cur.putbackPos; - final int oldPBL = cur.putbackLineNo; - final int oldLine = cur.lineNo; - final String rc = getLine(); - cur.peekedPos = cur.pos; - cur.peekedLineNo = cur.lineNo; - cur.pos = oldPos; - cur.lineNo = oldLine; - cur.putbackPos = oldPB; - cur.putbackLineNo = oldPBL; - return rc; - } - - /** - Only valid after calling peekLine() and before calling getLine(). - This places the cursor to the position it would have been at had - the peekLine() had been fetched with getLine(). - */ - public void consumePeeked(){ - cur.pos = cur.peekedPos; - cur.lineNo = cur.peekedLineNo; - } - - /** - Restores the cursor to the position it had before the previous - call to getLine(). - */ - public void putbackLine(){ - cur.pos = cur.putbackPos; - cur.lineNo = cur.putbackLineNo; - } - - private static final Pattern patternRequiredProperties = - Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); - private static final Pattern patternScriptModuleName = - Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); - private static final Pattern patternMixedModuleName = - Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); - private static final Pattern patternCommand = - Pattern.compile("^--(([a-z-]+)( .*)?)$"); - - /** - Looks for "directives." If a compatible one is found, it is - processed and this function returns. If an incompatible one is found, - a description of it is returned and processing of the test must - end immediately. - */ - private void checkForDirective(String line) throws IncompatibleDirective { - if(line.startsWith("#")){ - throw new IncompatibleDirective(this, "C-preprocessor input: "+line); - }else if(line.startsWith("---")){ - new IncompatibleDirective(this, "triple-dash: "+line); - } - Matcher m = patternScriptModuleName.matcher(line); - if( m.find() ){ - moduleName = m.group(1); - return; - } - m = patternRequiredProperties.matcher(line); - if( m.find() ){ - throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); - } - m = patternMixedModuleName.matcher(line); - if( m.find() ){ - throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); - } - if( line.indexOf("\n|")>=0 ){ - throw new IncompatibleDirective(this, "newline-pipe combination."); - } - return; - } - - public boolean isCommandLine(String line, boolean checkForImpl){ - final Matcher m = patternCommand.matcher(line); - boolean rc = m.find(); - if( rc && checkForImpl ){ - rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); - } - return rc; - } - - /** - If line looks like a command, returns an argv for that command - invocation, else returns null. - */ - public String[] getCommandArgv(String line){ - final Matcher m = patternCommand.matcher(line); - return m.find() ? m.group(1).trim().split("\\s+") : null; - } - - /** - Fetches lines until the next recognized command. Throws if - checkForDirective() does. Returns null if there is no input or - it's only whitespace. The returned string retains all whitespace. - - Note that "subcommands", --command-like constructs in the body - which do not match a known command name are considered to be - content, not commands. - */ - public String fetchCommandBody(){ - final StringBuilder sb = new StringBuilder(); - String line; - while( (null != (line = peekLine())) ){ - checkForDirective(line); - if( !isCommandLine(line, true) ){ - sb.append(line).append("\n"); - consumePeeked(); - }else{ - break; - } - } - line = sb.toString(); - return line.trim().isEmpty() ? null : line; - } - - public void processCommand(SQLTester t, String[] argv) throws Exception{ - verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); - if(outer.getVerbosity()>1){ - final String input = t.getInputText(); - if( !input.isEmpty() ) verbose2("Input buffer = ",input); - } - CommandDispatcher2.dispatch(t, this, argv); - } - - public void toss(Object... msg) throws TestScript2Failed { - StringBuilder sb = new StringBuilder(); - for(Object s : msg) sb.append(s); - throw new TestScript2Failed(this, sb.toString()); - } - - /** - Runs this test script in the context of the given tester object. - */ - @SuppressWarnings("unchecked") - public boolean run(SQLTester tester) throws Exception { - reset(); - setVerbosity(tester.getVerbosity()); - String line, directive; - String[] argv; - while( null != (line = getLine()) ){ - //verbose(line); - checkForDirective(line); - argv = getCommandArgv(line); - if( null!=argv ){ - processCommand(tester, argv); - continue; - } - tester.appendInput(line,true); - } - return true; - } -} diff --git a/ext/jni/src/tests/000-000-sanity.test2 b/ext/jni/src/tests/000-000-sanity.test similarity index 76% rename from ext/jni/src/tests/000-000-sanity.test2 rename to ext/jni/src/tests/000-000-sanity.test index 3ad4c39680..905882dc3b 100644 --- a/ext/jni/src/tests/000-000-sanity.test2 +++ b/ext/jni/src/tests/000-000-sanity.test @@ -8,6 +8,7 @@ ** REQUIRED_PROPERTIES: ** */ +--print starting up 😃 --close all --oom --db 0 @@ -38,5 +39,13 @@ SELECT json_array(1,2,3) [1,2,3] {"a":1,"b":2} --end +--testcase col-names-on +--column-names 1 + select 1 as 'a', 2 as 'b'; +--result a 1 b 2 +--testcase col-names-off +--column-names 0 + select 1 as 'a', 2 as 'b'; +--result 1 2 --close ---print 🤩😃 the end +--print reached the end 😃 diff --git a/ext/jni/src/tests/010_ignored.test b/ext/jni/src/tests/000-001-ignored.test similarity index 100% rename from ext/jni/src/tests/010_ignored.test rename to ext/jni/src/tests/000-001-ignored.test diff --git a/ext/jni/src/tests/000_first.test b/ext/jni/src/tests/000_first.test deleted file mode 100644 index bcd78a27a3..0000000000 --- a/ext/jni/src/tests/000_first.test +++ /dev/null @@ -1,68 +0,0 @@ -/* A script for testing the org.sqlite.jni.tester infrastructure -** -** SCRIPT_MODULE_NAME: 000_first -** -*/ - -junk - ---new SQLTester.db ---run - SELECT 1; - SELECT 2; --- comment --- uncomment to introduce intentional syntax error ---oom ---print These are args to the print command. -This is from the print command's body. ---print -Also from the print command. ---- also ignored ---testcase 1 - SELECT 'a b', 'c'; - SELECT 'd', 'e'; - SELECT '{}', 'f'; - SELECT '{ }', 'g'; - SELECT '(a-b-c)', '[a-b-c]'; - -- this comment must not cause an error ---result {a b} c d e "{}" f "{\011}" g (a-b-c) [a-b-c] ---testcase 2 - SELECT 123 ---glob 1# ---testcase 3 - SELECT 'a' ---notglob # ---close ---open SQLTester.db ---print Re-opened db. ---testcase fourth - SELECT 1, 2; - SELECT 'b', 'c'; ---tableresult - [0-9] # - b c ---end ---null zilch ---testcase null-command - SELECT null; ---result zilch ---testcase json-array-1 -SELECT json_array(1,2,3) ---json [1,2,3] ---testcase json-array-2 - SELECT json_array(1,2,3); - SELECT json_object('a',1,'b',2); ---json-block -[1,2,3] -{"a":1,"b":2} ---end ---testcase table-result-globs - SELECT 123; - SELECT 'aBc'; - SELECT 456; ---tableresult - # - [a-z][A-Z][a-z] - 4# ---end ---an-unknown-command diff --git a/manifest b/manifest index 8119a67605..8b758a7b5f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Port\sthe\sSQLTester\s'v1'\scommands\sto\sthe\s'v2'\sevaluation\sbits.\sStill\sTODO\sis\sswapping\sout\sv1\swith\sthese\sseparate\simpls. -D 2023-08-09T23:47:14.521 +C Replace\sthe\sSQLTester\sinfrastructure\swith\sa\sline-oriented,\snon-regex-heavy\sparser.\sAdd\s--column-names\scommand. +D 2023-08-10T00:34:38.136 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -230,7 +230,7 @@ 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 d7300b7e124214afde7f11bddd5c0d336a9be0220fe2b74e787078e1aa2db778 +F ext/jni/GNUmakefile 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520 F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328 @@ -266,13 +266,11 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1ae38d872d2cb582e1a1abd67b5e9c276bf2f610cacc918428b63c668131642e -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 463021981a65ffe7147a1bfada557b275b0cba3c33176ac328502ff09d146f28 -F ext/jni/src/org/sqlite/jni/tester/TestScript2.java 25895a534a1e4634268beecd1a689bdfc0aafbfe32071c27b5189ccb8aeec31e +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6e4a1f78291f9b76284035dacc3d77a85f8d1a8791d7acaf201deffd771d354 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 496b402c7faedf18be41542c6dc77c19f2735663821a5973639eb614e33aa707 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test2 dfbcccc7b3548ae56deb2ef8fe17dd9235a81fbd552536ef9202284549c7fcf3 -F ext/jni/src/tests/000_first.test cd5fb732520cf36d7a3e5ad94a274c7327a9504b01a1a7f98e1f946df6c539fd -F ext/jni/src/tests/010_ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 +F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 w ext/jni/src/tests/000-000-sanity.test2 +F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 w ext/jni/src/tests/010_ignored.test F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2092,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 7a19bef4f572a90fb7896b9360f9c72b052955ca9b0549be870b2b245c1f1b2b -R eed3d04c8636ba991a620a2a8f5a013d +P 0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 +R aaacd016e2b2dfc7887fd83bed604d98 U stephan -Z bdeddd00da26b413ab473bf39ad731fc +Z fb4c2d6a86f69138bd39103974c9d8cc # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d522acb938..2507153dee 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 \ No newline at end of file +88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d \ No newline at end of file From 3a0e45d0af9e8416e346edf9a48a292ba7854219 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 00:58:48 +0000 Subject: [PATCH 119/148] Remove --print's ability to read ahead. FossilOrigin-Name: 15cfee972bcbd857d18ea626c9a9da64b2a445516946f3dd83c75a4f6b8a92fa --- .../src/org/sqlite/jni/tester/TestScript.java | 6 +----- manifest | 16 ++++++++-------- manifest.uuid | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 8f6863eef0..78cb3237b1 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -208,15 +208,11 @@ class PrintCommand extends Command { SQLTester st, TestScript ts, String[] argv ) throws Exception{ st.out(ts.getOutputPrefix(),": "); - final String body = ts.fetchCommandBody(); - if( 1==argv.length && null==body ){ + if( 1==argv.length ){ st.out( st.getInputText() ); }else{ st.outln( Util.argvToString(argv) ); } - if( null!=body ){ - st.out(body); - } } } diff --git a/manifest b/manifest index 8b758a7b5f..4a6014b288 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Replace\sthe\sSQLTester\sinfrastructure\swith\sa\sline-oriented,\snon-regex-heavy\sparser.\sAdd\s--column-names\scommand. -D 2023-08-10T00:34:38.136 +C Remove\s--print's\sability\sto\sread\sahead. +D 2023-08-10T00:58:48.754 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -267,10 +267,10 @@ F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449 F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6e4a1f78291f9b76284035dacc3d77a85f8d1a8791d7acaf201deffd771d354 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 496b402c7faedf18be41542c6dc77c19f2735663821a5973639eb614e33aa707 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java ad41031370cafe404baebb836db24b499f9b57f903646da7a70b3550e0dd8dd0 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 w ext/jni/src/tests/000-000-sanity.test2 -F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 w ext/jni/src/tests/010_ignored.test +F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 +F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 F ext/lsm1/lsm-test/README 87ea529d2abe615e856d4714bfe8bb185e6c2771b8612aa6298588b7b43e6f86 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 0cf57e5b0f90779e450e9db1ca009610df5e6f4487337d49017636bde3bb02d6 -R aaacd016e2b2dfc7887fd83bed604d98 +P 88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d +R 952a45c93dc1bf5ba7d98bb1afbac238 U stephan -Z fb4c2d6a86f69138bd39103974c9d8cc +Z 43582e6a6d71574c725ca1af2b5f7e0a # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2507153dee..2445e3d3c9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d \ No newline at end of file +15cfee972bcbd857d18ea626c9a9da64b2a445516946f3dd83c75a4f6b8a92fa \ No newline at end of file From 50a17a1b3a5f2a8e108e9a4eea5fe9e68438d0ea Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 01:03:19 +0000 Subject: [PATCH 120/148] Treat all args to --glob as a single glob. FossilOrigin-Name: 4737bd345732e34cabbf90821e007d3027e68d6583c2e05e1e8bf5920373a6dc --- ext/jni/src/org/sqlite/jni/tester/TestScript.java | 4 ++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java index 78cb3237b1..c075ceb1a5 100644 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ b/ext/jni/src/org/sqlite/jni/tester/TestScript.java @@ -137,7 +137,7 @@ class GlobCommand extends Command { protected GlobCommand(boolean negate){ this.negate = negate; } public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); + argcCheck(ts,argv,1,-1); t.incrementTestCounter(); final String sql = t.takeInputBuffer(); int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, @@ -145,7 +145,7 @@ class GlobCommand extends Command { final String result = t.getResultText(); final String sArgs = Util.argvToString(argv); //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = argv[1]; + final String glob = Util.argvToString(argv); rc = SQLTester.strglob(glob, result); if( (negate && 0==rc) || (!negate && 0!=rc) ){ ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); diff --git a/manifest b/manifest index 4a6014b288..7a15348fb2 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\s--print's\sability\sto\sread\sahead. -D 2023-08-10T00:58:48.754 +C Treat\sall\sargs\sto\s--glob\sas\sa\ssingle\sglob. +D 2023-08-10T01:03:19.375 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449 F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6e4a1f78291f9b76284035dacc3d77a85f8d1a8791d7acaf201deffd771d354 -F ext/jni/src/org/sqlite/jni/tester/TestScript.java ad41031370cafe404baebb836db24b499f9b57f903646da7a70b3550e0dd8dd0 +F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9f172fcffae9935c6d1c9686b3f69df199a191b7280c926bf85fa8fa30ebbddf F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 88863908ee2059c2d18a095cbd91f41674c7b0d0a8864ec21715a5317054df4d -R 952a45c93dc1bf5ba7d98bb1afbac238 +P 15cfee972bcbd857d18ea626c9a9da64b2a445516946f3dd83c75a4f6b8a92fa +R d87c671bcff8c274e753f8136bcb0ba4 U stephan -Z 43582e6a6d71574c725ca1af2b5f7e0a +Z 3a5d0d87ef0d81bf62d0862f9674bbc7 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 2445e3d3c9..44b844827b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -15cfee972bcbd857d18ea626c9a9da64b2a445516946f3dd83c75a4f6b8a92fa \ No newline at end of file +4737bd345732e34cabbf90821e007d3027e68d6583c2e05e1e8bf5920373a6dc \ No newline at end of file From 2a9106514560f5502e1633acc6454c9a4eba1653 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 01:19:40 +0000 Subject: [PATCH 121/148] Defer static JNI-side init of SQLTester until main() is called so that its auto-extensions do not leak over to clients of the main library. FossilOrigin-Name: e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 2 +- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 431679c7ce..f2c5da3132 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -424,6 +424,7 @@ public class SQLTester { } public static void main(String[] argv) throws Exception{ + installCustomExtensions(); final SQLTester t = new SQLTester(); boolean v2 = false; for(String a : argv){ @@ -475,7 +476,6 @@ public class SQLTester { load that lib from here. The same load from SQLite3Jni does not happen early enough. Without this, installCustomExtensions() is an unresolved symbol. */; - installCustomExtensions(); } } diff --git a/manifest b/manifest index 671fa3e44d..83a8306b8c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\strunk\sinto\sjni\sbranch. -D 2023-08-10T01:05:28.422 +C Defer\sstatic\sJNI-side\sinit\sof\sSQLTester\suntil\smain()\sis\scalled\sso\sthat\sits\sauto-extensions\sdo\snot\sleak\sover\sto\sclients\sof\sthe\smain\slibrary. +D 2023-08-10T01:19:40.795 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e907859 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e6e4a1f78291f9b76284035dacc3d77a85f8d1a8791d7acaf201deffd771d354 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java a5d843ade1f4456cf5cd3d021f5593ff60c0d8786fb55a2d59d7a984738c299e F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9f172fcffae9935c6d1c9686b3f69df199a191b7280c926bf85fa8fa30ebbddf F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4737bd345732e34cabbf90821e007d3027e68d6583c2e05e1e8bf5920373a6dc aa6de539c09faa320b68c63659e602107145c4263fa680d5b40fe4d7d7ac4534 -R 3d8bf06cb5fd09f5007b52014737203d +P 52fa6f78414c41073431c166550806bb8a835bd38cfc1236c9363784c78b81b9 +R e4a7dc0518dd6056979335c8c6e73e83 U stephan -Z 31e6b9c8838dedba9bb6eecc51954ae6 +Z 125c5fe250744e87fc135cd6f93efe47 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8404ad041a..904da66b33 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -52fa6f78414c41073431c166550806bb8a835bd38cfc1236c9363784c78b81b9 \ No newline at end of file +e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 \ No newline at end of file From 0c6df29cba6b17a0c06c2b85f4899efdb4efde49 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 01:44:48 +0000 Subject: [PATCH 122/148] Move all of the SQLTester code into a single file, since it's only got 1 public class. Remove 'public' from many methods which don't need it. Add more documentation to it. FossilOrigin-Name: 2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 --- ext/jni/GNUmakefile | 6 +- ext/jni/src/org/sqlite/jni/tester/Outer.java | 60 -- .../src/org/sqlite/jni/tester/SQLTester.java | 799 +++++++++++++++++- .../src/org/sqlite/jni/tester/TestScript.java | 682 --------------- manifest | 16 +- manifest.uuid | 2 +- 6 files changed, 776 insertions(+), 789 deletions(-) delete mode 100644 ext/jni/src/org/sqlite/jni/tester/Outer.java delete mode 100644 ext/jni/src/org/sqlite/jni/tester/TestScript.java diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 9d78f9b0e6..8506014462 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -83,11 +83,7 @@ ifeq (1,$(enable.fts5)) TesterFts5.java \ ) endif -JAVA_FILES.tester := $(patsubst %,$(dir.src.jni.tester)/%,\ - Outer.java \ - SQLTester.java \ - TestScript.java \ -) +JAVA_FILES.tester := $(dir.src.jni.tester)/SQLTester.java CLASS_FILES.main := $(JAVA_FILES.main:.java=.class) CLASS_FILES.tester := $(JAVA_FILES.tester:.java=.class) diff --git a/ext/jni/src/org/sqlite/jni/tester/Outer.java b/ext/jni/src/org/sqlite/jni/tester/Outer.java deleted file mode 100644 index e6f90ddf31..0000000000 --- a/ext/jni/src/org/sqlite/jni/tester/Outer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* -** 2023-08-08 -** -** 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 utility class for generating console output. -*/ -package org.sqlite.jni.tester; - -/** - Console output utility class. -*/ -class Outer { - public int verbosity = 0; - - public static void out(Object val){ - System.out.print(val); - } - - public static void outln(Object val){ - System.out.println(val); - } - - @SuppressWarnings("unchecked") - public static void out(Object... vals){ - for(Object v : vals) out(v); - } - - @SuppressWarnings("unchecked") - public static void outln(Object... vals){ - out(vals); - out("\n"); - } - - @SuppressWarnings("unchecked") - public Outer verbose(Object... vals){ - if(verbosity>0){ - out("VERBOSE",(verbosity>1 ? "+: " : ": ")); - outln(vals); - } - return this; - } - - public void setVerbosity(int level){ - verbosity = level; - } - - public int getVerbosity(){ - return verbosity; - } - - public boolean isVerbose(){return verbosity > 0;} - -} diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index f2c5da3132..1e3443b718 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -20,12 +20,13 @@ import java.nio.charset.StandardCharsets; import java.util.regex.*; import org.sqlite.jni.*; import static org.sqlite.jni.SQLite3Jni.*; +import org.sqlite.jni.sqlite3; /** - Modes for how to handle SQLTester.execSql()'s - result output. - */ + Modes for how to escape (or not) column values and names from + SQLTester.execSql() to the result buffer output. +*/ enum ResultBufferMode { //! Do not append to result buffer NONE, @@ -35,6 +36,10 @@ enum ResultBufferMode { ASIS }; +/** + Modes to specify how to emit multi-row output from + SQLTester.execSql() to the result buffer. +*/ enum ResultRowMode { //! Keep all result rows on one line, space-separated. ONELINE, @@ -48,6 +53,71 @@ class SQLTesterException extends RuntimeException { } } +class TestScriptFailed extends SQLTesterException { + public TestScriptFailed(TestScript ts, String msg){ + super(ts.getOutputPrefix()+": "+msg); + } +} + +class UnknownCommand extends SQLTesterException { + public UnknownCommand(TestScript ts, String cmd){ + super(ts.getOutputPrefix()+": unknown command: "+cmd); + } +} + +class IncompatibleDirective extends SQLTesterException { + public IncompatibleDirective(TestScript ts, String line){ + super(ts.getOutputPrefix()+": incompatible directive: "+line); + } +} + +/** + Console output utility class. +*/ +class Outer { + private int verbosity = 0; + + static void out(Object val){ + System.out.print(val); + } + + static void outln(Object val){ + System.out.println(val); + } + + @SuppressWarnings("unchecked") + Outer out(Object... vals){ + for(Object v : vals) out(v); + return this; + } + + @SuppressWarnings("unchecked") + Outer outln(Object... vals){ + out(vals).out("\n"); + return this; + } + + @SuppressWarnings("unchecked") + Outer verbose(Object... vals){ + if(verbosity>0){ + out("VERBOSE",(verbosity>1 ? "+: " : ": ")); + outln(vals); + } + return this; + } + + void setVerbosity(int level){ + verbosity = level; + } + + int getVerbosity(){ + return verbosity; + } + + public boolean isVerbose(){return verbosity > 0;} + +} + /** This class provides an application which aims to implement the rudimentary SQL-driven test tool described in the accompanying @@ -64,45 +134,54 @@ public class SQLTester { private final StringBuilder inputBuffer = new StringBuilder(); //! Test result buffer. private final StringBuilder resultBuffer = new StringBuilder(); - private String nullView; + //! Output representation of SQL NULL. + private String nullView = "nil"; + //! Total tests run. private int nTotalTest = 0; + //! Total test script files run. private int nTestFile = 0; + //! Number of scripts which were aborted. private int nAbortedScript = 0; - private int nTest; + //! Per-script test counter. + private int nTest = 0; + //! True to enable column name output from execSql() private boolean emitColNames; + //! The list of available db handles. private final sqlite3[] aDb = new sqlite3[7]; + //! Index into aDb of the current db. private int iCurrentDb = 0; + //! Name of the default db, re-created for each script. private final String initialDbName = "test.db"; - private TestScript currentScript; + public SQLTester(){ reset(); } - public void setVerbosity(int level){ + void setVerbosity(int level){ this.outer.setVerbosity( level ); } - public int getVerbosity(){ + int getVerbosity(){ return this.outer.getVerbosity(); } - public boolean isVerbose(){ + boolean isVerbose(){ return this.outer.isVerbose(); } void outputColumnNames(boolean b){ emitColNames = b; } @SuppressWarnings("unchecked") - public void verbose(Object... vals){ + void verbose(Object... vals){ outer.verbose(vals); } @SuppressWarnings("unchecked") - public void outln(Object... vals){ + void outln(Object... vals){ outer.outln(vals); } @SuppressWarnings("unchecked") - public void out(Object... vals){ + void out(Object... vals){ outer.out(vals); } @@ -112,16 +191,12 @@ public class SQLTester { //verbose("Added file ",filename); } - public void setupInitialDb() throws Exception { + private void setupInitialDb() throws Exception { Util.unlink(initialDbName); openDb(0, initialDbName, true); } - TestScript getCurrentScript(){ - return currentScript; - } - - private void runTests() throws Exception { + public void runTests() throws Exception { for(String f : listInFiles){ reset(); setupInitialDb(); @@ -270,10 +345,12 @@ public class SQLTester { void incrementTestCounter(){ ++nTest; ++nTotalTest; } + //! "Special" characters - we have to escape output if it contains any. static final Pattern patternSpecial = Pattern.compile( - "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]", Pattern.MULTILINE + "[\\x00-\\x20\\x22\\x5c\\x7b\\x7d]" ); - static final Pattern patternSquiggly = Pattern.compile("[{}]", Pattern.MULTILINE); + //! Either of '{' or '}'. + static final Pattern patternSquiggly = Pattern.compile("[{}]"); /** Returns v or some escaped form of v, as defined in the tester's @@ -317,6 +394,18 @@ public class SQLTester { } } + /** + Runs SQL on behalf of test commands and outputs the results following + the very specific rules of the test framework. + + If db is null, getCurrentDb() is assumed. If throwOnError is true then + any db-side error will result in an exception, else they result in + the db's result code. + + appendMode specifies how/whether to append results to the result + buffer. lineMode specifies whether to output all results in a + single line or one line per row. + */ public int execSql(sqlite3 db, boolean throwOnError, ResultBufferMode appendMode, ResultRowMode lineMode, @@ -462,14 +551,19 @@ public class SQLTester { of digits, e.g. "#23" or "1#3", but will match at the end, e.g. "12#". */ - public static int strglob(String glob, String txt){ + static int strglob(String glob, String txt){ return strglob( (glob+"\0").getBytes(StandardCharsets.UTF_8), (txt+"\0").getBytes(StandardCharsets.UTF_8) ); } - private static native void installCustomExtensions(); + /** + Sets up C-side components needed by the test framework. This must + not be called until main() is triggered so that it does not + interfere with library clients who don't use this class. + */ + static native void installCustomExtensions(); static { System.loadLibrary("sqlite3-jni") /* Interestingly, when SQLTester is the main app, we have to @@ -486,7 +580,7 @@ public class SQLTester { final class Util { //! Throws a new T, appending all msg args into a string for the message. - public static void toss(Class errorType, Object... msg) throws Exception { + static void toss(Class errorType, Object... msg) throws Exception { StringBuilder sb = new StringBuilder(); for(Object s : msg) sb.append(s); final java.lang.reflect.Constructor ctor = @@ -494,16 +588,12 @@ final class Util { throw ctor.newInstance(sb.toString()); } - public static void toss(Object... msg) throws Exception{ + static void toss(Object... msg) throws Exception{ toss(RuntimeException.class, msg); } - public static void badArg(Object... msg) throws Exception{ - toss(IllegalArgumentException.class, msg); - } - //! Tries to delete the given file, silently ignoring failure. - public static void unlink(String filename){ + static void unlink(String filename){ try{ final java.io.File f = new java.io.File(filename); f.delete(); @@ -514,10 +604,10 @@ final class Util { /** Appends all entries in argv[1..end] into a space-separated - string, argv[0] is not included because it's expected to - be a command name. + string, argv[0] is not included because it's expected to be a + command name. */ - public static String argvToString(String[] argv){ + static String argvToString(String[] argv){ StringBuilder sb = new StringBuilder(); for(int i = 1; i < argv.length; ++i ){ if( i>1 ) sb.append(" "); @@ -527,3 +617,648 @@ final class Util { } } + +/** + Base class for test script commands. It provides a set of utility + APIs for concrete command implementations. + + Each subclass must have a public no-arg ctor and must implement + the process() method which is abstract in this class. + + Commands are intended to be stateless, except perhaps for counters + and similar internals. Specifically, no state which changes the + behavior between any two invocations of process() should be + retained. +*/ +abstract class Command { + protected Command(){} + + /** + Must process one command-unit of work and either return + (on success) or throw (on error). + + The first two arguments specify the context of the test. + + argv is a list with the command name followed by any arguments to + that command. The argcCheck() method from this class provides + very basic argc validation. + */ + public abstract void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception; + + /** + If argv.length-1 (-1 because the command's name is in argv[0]) does not + fall in the inclusive range (min,max) then this function throws. Use + a max value of -1 to mean unlimited. + */ + protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{ + int argc = argv.length-1; + if(argc=0 && argc>max)){ + if( min==max ){ + ts.toss(argv[0]," requires exactly ",min," argument(s)"); + }else if(max>0){ + ts.toss(argv[0]," requires ",min,"-",max," arguments."); + }else{ + ts.toss(argv[0]," requires at least ",min," arguments."); + } + } + } + + /** + Equivalent to argcCheck(argv,argc,argc). + */ + protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ + argcCheck(ts, argv, argc, argc); + } +} + +//! --close command +class CloseDbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + Integer id; + if(argv.length>1){ + String arg = argv[1]; + if("all".equals(arg)){ + t.closeAllDbs(); + return; + } + else{ + id = Integer.parseInt(arg); + } + }else{ + id = t.getCurrentDbId(); + } + t.closeDb(id); + } +} + +//! --column-names command +class ColumnNamesCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); + } +} + +//! --db command +class DbCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.setCurrentDb( Integer.parseInt(argv[1]) ); + } +} + +//! --glob command +class GlobCommand extends Command { + private boolean negate = false; + public GlobCommand(){} + protected GlobCommand(boolean negate){ this.negate = negate; } + + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, + ResultRowMode.ONELINE, sql); + final String result = t.getResultText(); + final String sArgs = Util.argvToString(argv); + //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + final String glob = Util.argvToString(argv); + rc = SQLTester.strglob(glob, result); + if( (negate && 0==rc) || (!negate && 0!=rc) ){ + ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); + } + } +} + +//! --json command +class JsonCommand extends ResultCommand { + public JsonCommand(){ super(ResultBufferMode.ASIS); } +} + +//! --json-block command +class JsonBlockCommand extends TableResultCommand { + public JsonBlockCommand(){ super(true); } +} + +//! --new command +class NewDbCommand extends OpenDbCommand { + public NewDbCommand(){ super(true); } +} + +//! Placeholder dummy/no-op commands +class NoopCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + } +} + +//! --notglob command +class NotGlobCommand extends GlobCommand { + public NotGlobCommand(){ + super(true); + } +} + +//! --null command +class NullCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + argcCheck(ts,argv,1); + st.setNullValue( argv[1] ); + } +} + +//! --open command +class OpenDbCommand extends Command { + private boolean createIfNeeded = false; + public OpenDbCommand(){} + protected OpenDbCommand(boolean c){createIfNeeded = c;} + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + t.openDb(argv[1], createIfNeeded); + } +} + +//! --print command +class PrintCommand extends Command { + public void process( + SQLTester st, TestScript ts, String[] argv + ) throws Exception{ + st.out(ts.getOutputPrefix(),": "); + if( 1==argv.length ){ + st.out( st.getInputText() ); + }else{ + st.outln( Util.argvToString(argv) ); + } + } +} + +//! --result command +class ResultCommand extends Command { + private final ResultBufferMode bufferMode; + protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } + public ResultCommand(){ this(ResultBufferMode.ESCAPED); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,-1); + t.incrementTestCounter(); + final String sql = t.takeInputBuffer(); + //t.verbose(argv[0]," SQL =\n",sql); + int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); + final String result = t.getResultText().trim(); + final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; + if( !result.equals(sArgs) ){ + t.outln(argv[0]," FAILED comparison. Result buffer:\n", + result,"\nargs:\n",sArgs); + ts.toss(argv[0]+" comparison failed."); + } + } +} + +//! --run command +class RunCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0,1); + final sqlite3 db = (1==argv.length) + ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); + final String sql = t.takeInputBuffer(); + int rc = t.execSql(db, false, ResultBufferMode.NONE, + ResultRowMode.ONELINE, sql); + if( 0!=rc && t.isVerbose() ){ + String msg = sqlite3_errmsg(db); + t.verbose(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); + } + } +} + +//! --tableresult command +class TableResultCommand extends Command { + private final boolean jsonMode; + protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } + public TableResultCommand(){ this(false); } + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,0); + t.incrementTestCounter(); + String body = ts.fetchCommandBody(); + if( null==body ) ts.toss("Missing ",argv[0]," body."); + body = body.trim(); + if( !body.endsWith("\n--end") ){ + ts.toss(argv[0], " must be terminated with --end."); + }else{ + int n = body.length(); + body = body.substring(0, n-6); + } + final String[] globs = body.split("\\s*\\n\\s*"); + if( globs.length < 1 ){ + ts.toss(argv[0], " requires 1 or more ", + (jsonMode ? "json snippets" : "globs"),"."); + } + final String sql = t.takeInputBuffer(); + t.execSql(null, true, + jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, + ResultRowMode.NEWLINE, sql); + final String rbuf = t.getResultText(); + final String[] res = rbuf.split("\n"); + if( res.length != globs.length ){ + ts.toss(argv[0], " failure: input has ", res.length, + " row(s) but expecting ",globs.length); + } + for(int i = 0; i < res.length; ++i){ + final String glob = globs[i].replaceAll("\\s+"," ").trim(); + //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + if( jsonMode ){ + if( !glob.equals(res[i]) ){ + ts.toss(argv[0], " json <<",glob, ">> does not match: <<", + res[i],">>"); + } + }else if( 0 != SQLTester.strglob(glob, res[i]) ){ + ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); + } + } + } +} + +//! --testcase command +class TestCaseCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + // TODO?: do something with the test name + t.clearResultBuffer(); + t.clearInputBuffer(); + } +} + +class CommandDispatcher2 { + + private static java.util.Map commandMap = + new java.util.HashMap<>(); + + /** + Returns a (cached) instance mapped to name, or null if no match + is found. + */ + static Command getCommandByName(String name){ + Command rv = commandMap.get(name); + if( null!=rv ) return rv; + switch(name){ + case "close": rv = new CloseDbCommand(); break; + case "column-names":rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + default: rv = null; break; + } + if( null!=rv ) commandMap.put(name, rv); + return rv; + } + + /** + Treats argv[0] as a command name, looks it up with + getCommandByName(), and calls process() on that instance, passing + it arguments given to this function. + */ + static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{ + final Command cmd = getCommandByName(argv[0]); + if(null == cmd){ + throw new UnknownCommand(ts, argv[0]); + } + cmd.process(tester, ts, argv); + } +} + + +/** + This class represents a single test script. It handles (or + delegates) its the reading-in and parsing, but the details of + evaluation are delegated elsewhere. +*/ +class TestScript { + private String filename = null; + private String moduleName = null; + private final Cursor cur = new Cursor(); + private final Outer outer = new Outer(); + + private static final class Cursor { + private final StringBuilder sb = new StringBuilder(); + byte[] src = null; + int pos = 0; + int putbackPos = 0; + int putbackLineNo = 0; + int lineNo = 0 /* yes, zero */; + int peekedPos = 0; + int peekedLineNo = 0; + boolean inComment = false; + + void reset(){ + sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false; + } + } + + private byte[] readFile(String filename) throws Exception { + return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); + } + + /** + Initializes the script with the content of the given file. + Throws if it cannot read the file. + */ + public TestScript(String filename) throws Exception{ + this.filename = filename; + setVerbosity(2); + cur.src = readFile(filename); + } + + public String getFilename(){ + return filename; + } + + public String getModuleName(){ + return moduleName; + } + + public void setVerbosity(int level){ + outer.setVerbosity(level); + } + + public String getOutputPrefix(){ + return "["+(moduleName==null ? filename : moduleName)+"] line "+ + cur.lineNo; + } + @SuppressWarnings("unchecked") + private TestScript verboseN(int level, Object... vals){ + final int verbosity = outer.getVerbosity(); + if(verbosity>=level){ + outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), + getOutputPrefix(),": ") + .outln(vals); + } + return this; + } + + private TestScript verbose1(Object... vals){return verboseN(1,vals);} + private TestScript verbose2(Object... vals){return verboseN(2,vals);} + + @SuppressWarnings("unchecked") + public TestScript warn(Object... vals){ + outer.out("WARNING ", getOutputPrefix(),": ") + .outln(vals); + return this; + } + + private void reset(){ + cur.reset(); + } + + + /** + Returns the next line from the buffer, minus the trailing EOL. + + Returns null when all input is consumed. Throws if it reads + illegally-encoded input, e.g. (non-)characters in the range + 128-256. + */ + String getLine(){ + if( cur.pos==cur.src.length ){ + return null /* EOF */; + } + cur.putbackPos = cur.pos; + cur.putbackLineNo = cur.lineNo; + cur.sb.setLength(0); + final boolean skipLeadingWs = false; + byte b = 0, prevB = 0; + int i = cur.pos; + if(skipLeadingWs) { + /* Skip any leading spaces, including newlines. This will eliminate + blank lines. */ + for(; i < cur.src.length; ++i, prevB=b){ + b = cur.src[i]; + switch((int)b){ + case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; + case 10/*NL*/: ++cur.lineNo; continue; + default: break; + } + break; + } + if( i==cur.src.length ){ + return null /* EOF */; + } + } + boolean doBreak = false; + final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; + int nChar = 0 /* number of bytes in the char */; + for(; i < cur.src.length && !doBreak; ++i){ + b = cur.src[i]; + switch( (int)b ){ + case 13/*CR*/: continue; + case 10/*NL*/: + ++cur.lineNo; + if(cur.sb.length()>0) doBreak = true; + // Else it's an empty string + break; + default: + /* Multi-byte chars need to be gathered up and appended at + one time. Appending individual bytes to the StringBuffer + appends their integer value. */ + nChar = 1; + switch( b & 0xF0 ){ + case 0xC0: nChar = 2; break; + case 0xE0: nChar = 3; break; + case 0xF0: nChar = 4; break; + default: + if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); + break; + } + if( 1==nChar ){ + cur.sb.append((char)b); + }else{ + for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; + cur.sb.append(new String(Arrays.copyOf(aChar, nChar), + StandardCharsets.UTF_8)); + i += nChar-1; + } + break; + } + } + cur.pos = i; + final String rv = cur.sb.toString(); + if( i==cur.src.length && 0==rv.length() ){ + return null /* EOF */; + } + return rv; + }/*getLine()*/ + + /** + Fetches the next line then resets the cursor to its pre-call + state. consumePeeked() can be used to consume this peeked line + without having to re-parse it. + */ + String peekLine(){ + final int oldPos = cur.pos; + final int oldPB = cur.putbackPos; + final int oldPBL = cur.putbackLineNo; + final int oldLine = cur.lineNo; + final String rc = getLine(); + cur.peekedPos = cur.pos; + cur.peekedLineNo = cur.lineNo; + cur.pos = oldPos; + cur.lineNo = oldLine; + cur.putbackPos = oldPB; + cur.putbackLineNo = oldPBL; + return rc; + } + + /** + Only valid after calling peekLine() and before calling getLine(). + This places the cursor to the position it would have been at had + the peekLine() had been fetched with getLine(). + */ + void consumePeeked(){ + cur.pos = cur.peekedPos; + cur.lineNo = cur.peekedLineNo; + } + + /** + Restores the cursor to the position it had before the previous + call to getLine(). + */ + void putbackLine(){ + cur.pos = cur.putbackPos; + cur.lineNo = cur.putbackLineNo; + } + + private static final Pattern patternRequiredProperties = + Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); + private static final Pattern patternScriptModuleName = + Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternMixedModuleName = + Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); + private static final Pattern patternCommand = + Pattern.compile("^--(([a-z-]+)( .*)?)$"); + + /** + Looks for "directives." If a compatible one is found, it is + processed and this function returns. If an incompatible one is found, + a description of it is returned and processing of the test must + end immediately. + */ + private void checkForDirective(String line) throws IncompatibleDirective { + if(line.startsWith("#")){ + throw new IncompatibleDirective(this, "C-preprocessor input: "+line); + }else if(line.startsWith("---")){ + new IncompatibleDirective(this, "triple-dash: "+line); + } + Matcher m = patternScriptModuleName.matcher(line); + if( m.find() ){ + moduleName = m.group(1); + return; + } + m = patternRequiredProperties.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); + } + m = patternMixedModuleName.matcher(line); + if( m.find() ){ + throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); + } + if( line.indexOf("\n|")>=0 ){ + throw new IncompatibleDirective(this, "newline-pipe combination."); + } + return; + } + + boolean isCommandLine(String line, boolean checkForImpl){ + final Matcher m = patternCommand.matcher(line); + boolean rc = m.find(); + if( rc && checkForImpl ){ + rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); + } + return rc; + } + + /** + If line looks like a command, returns an argv for that command + invocation, else returns null. + */ + String[] getCommandArgv(String line){ + final Matcher m = patternCommand.matcher(line); + return m.find() ? m.group(1).trim().split("\\s+") : null; + } + + /** + Fetches lines until the next recognized command. Throws if + checkForDirective() does. Returns null if there is no input or + it's only whitespace. The returned string retains all whitespace. + + Note that "subcommands", --command-like constructs in the body + which do not match a known command name are considered to be + content, not commands. + */ + String fetchCommandBody(){ + final StringBuilder sb = new StringBuilder(); + String line; + while( (null != (line = peekLine())) ){ + checkForDirective(line); + if( !isCommandLine(line, true) ){ + sb.append(line).append("\n"); + consumePeeked(); + }else{ + break; + } + } + line = sb.toString(); + return line.trim().isEmpty() ? null : line; + } + + private void processCommand(SQLTester t, String[] argv) throws Exception{ + verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); + if(outer.getVerbosity()>1){ + final String input = t.getInputText(); + if( !input.isEmpty() ) verbose2("Input buffer = ",input); + } + CommandDispatcher2.dispatch(t, this, argv); + } + + void toss(Object... msg) throws TestScriptFailed { + StringBuilder sb = new StringBuilder(); + for(Object s : msg) sb.append(s); + throw new TestScriptFailed(this, sb.toString()); + } + + /** + Runs this test script in the context of the given tester object. + */ + @SuppressWarnings("unchecked") + public boolean run(SQLTester tester) throws Exception { + reset(); + setVerbosity(tester.getVerbosity()); + String line, directive; + String[] argv; + while( null != (line = getLine()) ){ + //verbose(line); + checkForDirective(line); + argv = getCommandArgv(line); + if( null!=argv ){ + processCommand(tester, argv); + continue; + } + tester.appendInput(line,true); + } + return true; + } +} diff --git a/ext/jni/src/org/sqlite/jni/tester/TestScript.java b/ext/jni/src/org/sqlite/jni/tester/TestScript.java deleted file mode 100644 index c075ceb1a5..0000000000 --- a/ext/jni/src/org/sqlite/jni/tester/TestScript.java +++ /dev/null @@ -1,682 +0,0 @@ -/* -** 2023-08-08 -** -** 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 the TestScript part of the SQLTester framework. -*/ -package org.sqlite.jni.tester; -import static org.sqlite.jni.SQLite3Jni.*; -import org.sqlite.jni.sqlite3; -import java.util.Arrays; -import java.nio.charset.StandardCharsets; -import java.util.regex.*; - -class TestScriptFailed extends SQLTesterException { - public TestScriptFailed(TestScript ts, String msg){ - super(ts.getOutputPrefix()+": "+msg); - } -} - -class UnknownCommand extends SQLTesterException { - public UnknownCommand(TestScript ts, String cmd){ - super(ts.getOutputPrefix()+": unknown command: "+cmd); - } -} - -class IncompatibleDirective extends SQLTesterException { - public IncompatibleDirective(TestScript ts, String line){ - super(ts.getOutputPrefix()+": incompatible directive: "+line); - } -} - -/** - Base class for test script commands. It provides a set of utility - APIs for concrete command implementations. - - Each subclass must have a public no-arg ctor and must implement - the process() method which is abstract in this class. - - Commands are intended to be stateless, except perhaps for counters - and similar internals. Specifically, no state which changes the - behavior between any two invocations of process() should be - retained. -*/ -abstract class Command { - protected Command(){} - - /** - Must process one command-unit of work and either return - (on success) or throw (on error). - - The first two arguments specify the context of the test. - - argv is a list with the command name followed by any arguments to - that command. The argcCheck() method from this class provides - very basic argc validation. - */ - public abstract void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception; - - /** - If argv.length-1 (-1 because the command's name is in argv[0]) does not - fall in the inclusive range (min,max) then this function throws. Use - a max value of -1 to mean unlimited. - */ - protected final void argcCheck(TestScript ts, String[] argv, int min, int max) throws Exception{ - int argc = argv.length-1; - if(argc=0 && argc>max)){ - if( min==max ){ - ts.toss(argv[0]," requires exactly ",min," argument(s)"); - }else if(max>0){ - ts.toss(argv[0]," requires ",min,"-",max," arguments."); - }else{ - ts.toss(argv[0]," requires at least ",min," arguments."); - } - } - } - - /** - Equivalent to argcCheck(argv,argc,argc). - */ - protected final void argcCheck(TestScript ts, String[] argv, int argc) throws Exception{ - argcCheck(ts, argv, argc, argc); - } -} - -//! --close command -class CloseDbCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0,1); - Integer id; - if(argv.length>1){ - String arg = argv[1]; - if("all".equals(arg)){ - t.closeAllDbs(); - return; - } - else{ - id = Integer.parseInt(arg); - } - }else{ - id = t.getCurrentDbId(); - } - t.closeDb(id); - } -} - -//! --column-names command -class ColumnNamesCommand extends Command { - public void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception{ - argcCheck(ts,argv,1); - st.outputColumnNames( Integer.parseInt(argv[1])!=0 ); - } -} - -//! --db command -class DbCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); - t.setCurrentDb( Integer.parseInt(argv[1]) ); - } -} - -//! --glob command -class GlobCommand extends Command { - private boolean negate = false; - public GlobCommand(){} - protected GlobCommand(boolean negate){ this.negate = negate; } - - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1,-1); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - int rc = t.execSql(null, true, ResultBufferMode.ESCAPED, - ResultRowMode.ONELINE, sql); - final String result = t.getResultText(); - final String sArgs = Util.argvToString(argv); - //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); - final String glob = Util.argvToString(argv); - rc = SQLTester.strglob(glob, result); - if( (negate && 0==rc) || (!negate && 0!=rc) ){ - ts.toss(argv[0], " mismatch: ", glob," vs input: ",result); - } - } -} - -//! --json command -class JsonCommand extends ResultCommand { - public JsonCommand(){ super(ResultBufferMode.ASIS); } -} - -//! --json-block command -class JsonBlockCommand extends TableResultCommand { - public JsonBlockCommand(){ super(true); } -} - -//! --new command -class NewDbCommand extends OpenDbCommand { - public NewDbCommand(){ super(true); } -} - -//! Placeholder dummy/no-op commands -class NoopCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - } -} - -//! --notglob command -class NotGlobCommand extends GlobCommand { - public NotGlobCommand(){ - super(true); - } -} - -//! --null command -class NullCommand extends Command { - public void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception{ - argcCheck(ts,argv,1); - st.setNullValue( argv[1] ); - } -} - -//! --open command -class OpenDbCommand extends Command { - private boolean createIfNeeded = false; - public OpenDbCommand(){} - protected OpenDbCommand(boolean c){createIfNeeded = c;} - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); - t.openDb(argv[1], createIfNeeded); - } -} - -//! --print command -class PrintCommand extends Command { - public void process( - SQLTester st, TestScript ts, String[] argv - ) throws Exception{ - st.out(ts.getOutputPrefix(),": "); - if( 1==argv.length ){ - st.out( st.getInputText() ); - }else{ - st.outln( Util.argvToString(argv) ); - } - } -} - -//! --result command -class ResultCommand extends Command { - private final ResultBufferMode bufferMode; - protected ResultCommand(ResultBufferMode bm){ bufferMode = bm; } - public ResultCommand(){ this(ResultBufferMode.ESCAPED); } - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0,-1); - t.incrementTestCounter(); - final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); - int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); - final String result = t.getResultText().trim(); - final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; - if( !result.equals(sArgs) ){ - t.outln(argv[0]," FAILED comparison. Result buffer:\n", - result,"\nargs:\n",sArgs); - ts.toss(argv[0]+" comparison failed."); - } - } -} - -//! --run command -class RunCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0,1); - final sqlite3 db = (1==argv.length) - ? t.getCurrentDb() : t.getDbById( Integer.parseInt(argv[1]) ); - final String sql = t.takeInputBuffer(); - int rc = t.execSql(db, false, ResultBufferMode.NONE, - ResultRowMode.ONELINE, sql); - if( 0!=rc && t.isVerbose() ){ - String msg = sqlite3_errmsg(db); - t.verbose(argv[0]," non-fatal command error #",rc,": ", - msg,"\nfor SQL:\n",sql); - } - } -} - -//! --tableresult command -class TableResultCommand extends Command { - private final boolean jsonMode; - protected TableResultCommand(boolean jsonMode){ this.jsonMode = jsonMode; } - public TableResultCommand(){ this(false); } - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,0); - t.incrementTestCounter(); - String body = ts.fetchCommandBody(); - if( null==body ) ts.toss("Missing ",argv[0]," body."); - body = body.trim(); - if( !body.endsWith("\n--end") ){ - ts.toss(argv[0], " must be terminated with --end."); - }else{ - int n = body.length(); - body = body.substring(0, n-6); - } - final String[] globs = body.split("\\s*\\n\\s*"); - if( globs.length < 1 ){ - ts.toss(argv[0], " requires 1 or more ", - (jsonMode ? "json snippets" : "globs"),"."); - } - final String sql = t.takeInputBuffer(); - t.execSql(null, true, - jsonMode ? ResultBufferMode.ASIS : ResultBufferMode.ESCAPED, - ResultRowMode.NEWLINE, sql); - final String rbuf = t.getResultText(); - final String[] res = rbuf.split("\n"); - if( res.length != globs.length ){ - ts.toss(argv[0], " failure: input has ", res.length, - " row(s) but expecting ",globs.length); - } - for(int i = 0; i < res.length; ++i){ - final String glob = globs[i].replaceAll("\\s+"," ").trim(); - //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); - if( jsonMode ){ - if( !glob.equals(res[i]) ){ - ts.toss(argv[0], " json <<",glob, ">> does not match: <<", - res[i],">>"); - } - }else if( 0 != SQLTester.strglob(glob, res[i]) ){ - ts.toss(argv[0], " glob <<",glob,">> does not match: <<",res[i],">>"); - } - } - } -} - -//! --testcase command -class TestCaseCommand extends Command { - public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ - argcCheck(ts,argv,1); - // TODO?: do something with the test name - t.clearResultBuffer(); - t.clearInputBuffer(); - } -} - -class CommandDispatcher2 { - - private static java.util.Map commandMap = - new java.util.HashMap<>(); - - /** - Returns a (cached) instance mapped to name, or null if no match - is found. - */ - static Command getCommandByName(String name){ - Command rv = commandMap.get(name); - if( null!=rv ) return rv; - switch(name){ - case "close": rv = new CloseDbCommand(); break; - case "column-names":rv = new ColumnNamesCommand(); break; - case "db": rv = new DbCommand(); break; - case "glob": rv = new GlobCommand(); break; - case "json": rv = new JsonCommand(); break; - case "json-block": rv = new JsonBlockCommand(); break; - case "new": rv = new NewDbCommand(); break; - case "notglob": rv = new NotGlobCommand(); break; - case "null": rv = new NullCommand(); break; - case "oom": rv = new NoopCommand(); break; - case "open": rv = new OpenDbCommand(); break; - case "print": rv = new PrintCommand(); break; - case "result": rv = new ResultCommand(); break; - case "run": rv = new RunCommand(); break; - case "tableresult": rv = new TableResultCommand(); break; - case "testcase": rv = new TestCaseCommand(); break; - default: rv = null; break; - } - if( null!=rv ) commandMap.put(name, rv); - return rv; - } - - /** - Treats argv[0] as a command name, looks it up with - getCommandByName(), and calls process() on that instance, passing - it arguments given to this function. - */ - static void dispatch(SQLTester tester, TestScript ts, String[] argv) throws Exception{ - final Command cmd = getCommandByName(argv[0]); - if(null == cmd){ - throw new UnknownCommand(ts, argv[0]); - } - cmd.process(tester, ts, argv); - } -} - - -/** - This class represents a single test script. It handles (or - delegates) its the reading-in and parsing, but the details of - evaluation are delegated elsewhere. -*/ -class TestScript { - private String filename = null; - private String moduleName = null; - private final Cursor cur = new Cursor(); - private final Outer outer = new Outer(); - - private static final class Cursor { - private final StringBuilder sb = new StringBuilder(); - byte[] src = null; - int pos = 0; - int putbackPos = 0; - int putbackLineNo = 0; - int lineNo = 0 /* yes, zero */; - int peekedPos = 0; - int peekedLineNo = 0; - boolean inComment = false; - - void reset(){ - sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false; - } - } - - private byte[] readFile(String filename) throws Exception { - return java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(filename)); - } - - /** - Initializes the script with the content of the given file. - Throws if it cannot read the file. - */ - public TestScript(String filename) throws Exception{ - this.filename = filename; - setVerbosity(2); - cur.src = readFile(filename); - } - - public String getFilename(){ - return filename; - } - - public String getModuleName(){ - return moduleName; - } - - public void setVerbosity(int level){ - outer.setVerbosity(level); - } - - public String getOutputPrefix(){ - return "["+(moduleName==null ? filename : moduleName)+"] line "+ - cur.lineNo; - } - @SuppressWarnings("unchecked") - private TestScript verboseN(int level, Object... vals){ - final int verbosity = outer.getVerbosity(); - if(verbosity>=level){ - outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), - getOutputPrefix(),": "); - outer.outln(vals); - } - return this; - } - - private TestScript verbose1(Object... vals){return verboseN(1,vals);} - private TestScript verbose2(Object... vals){return verboseN(2,vals);} - - @SuppressWarnings("unchecked") - public TestScript warn(Object... vals){ - outer.out("WARNING ", getOutputPrefix(),": "); - outer.outln(vals); - return this; - } - - private void reset(){ - cur.reset(); - } - - - /** - Returns the next line from the buffer, minus the trailing EOL. - - Returns null when all input is consumed. Throws if it reads - illegally-encoded input, e.g. (non-)characters in the range - 128-256. - */ - String getLine(){ - if( cur.pos==cur.src.length ){ - return null /* EOF */; - } - cur.putbackPos = cur.pos; - cur.putbackLineNo = cur.lineNo; - cur.sb.setLength(0); - final boolean skipLeadingWs = false; - byte b = 0, prevB = 0; - int i = cur.pos; - if(skipLeadingWs) { - /* Skip any leading spaces, including newlines. This will eliminate - blank lines. */ - for(; i < cur.src.length; ++i, prevB=b){ - b = cur.src[i]; - switch((int)b){ - case 32/*space*/: case 9/*tab*/: case 13/*CR*/: continue; - case 10/*NL*/: ++cur.lineNo; continue; - default: break; - } - break; - } - if( i==cur.src.length ){ - return null /* EOF */; - } - } - boolean doBreak = false; - final byte[] aChar = {0,0,0,0} /* multi-byte char buffer */; - int nChar = 0 /* number of bytes in the char */; - for(; i < cur.src.length && !doBreak; ++i){ - b = cur.src[i]; - switch( (int)b ){ - case 13/*CR*/: continue; - case 10/*NL*/: - ++cur.lineNo; - if(cur.sb.length()>0) doBreak = true; - // Else it's an empty string - break; - default: - /* Multi-byte chars need to be gathered up and appended at - one time. Appending individual bytes to the StringBuffer - appends their integer value. */ - nChar = 1; - switch( b & 0xF0 ){ - case 0xC0: nChar = 2; break; - case 0xE0: nChar = 3; break; - case 0xF0: nChar = 4; break; - default: - if( b > 127 ) this.toss("Invalid character (#"+(int)b+")."); - break; - } - if( 1==nChar ){ - cur.sb.append((char)b); - }else{ - for(int x = 0; x < nChar; ++x) aChar[x] = cur.src[i+x]; - cur.sb.append(new String(Arrays.copyOf(aChar, nChar), - StandardCharsets.UTF_8)); - i += nChar-1; - } - break; - } - } - cur.pos = i; - final String rv = cur.sb.toString(); - if( i==cur.src.length && 0==rv.length() ){ - return null /* EOF */; - } - return rv; - }/*getLine()*/ - - /** - Fetches the next line then resets the cursor to its pre-call - state. consumePeeked() can be used to consume this peeked line - without having to re-parse it. - */ - String peekLine(){ - final int oldPos = cur.pos; - final int oldPB = cur.putbackPos; - final int oldPBL = cur.putbackLineNo; - final int oldLine = cur.lineNo; - final String rc = getLine(); - cur.peekedPos = cur.pos; - cur.peekedLineNo = cur.lineNo; - cur.pos = oldPos; - cur.lineNo = oldLine; - cur.putbackPos = oldPB; - cur.putbackLineNo = oldPBL; - return rc; - } - - /** - Only valid after calling peekLine() and before calling getLine(). - This places the cursor to the position it would have been at had - the peekLine() had been fetched with getLine(). - */ - void consumePeeked(){ - cur.pos = cur.peekedPos; - cur.lineNo = cur.peekedLineNo; - } - - /** - Restores the cursor to the position it had before the previous - call to getLine(). - */ - void putbackLine(){ - cur.pos = cur.putbackPos; - cur.lineNo = cur.putbackLineNo; - } - - private static final Pattern patternRequiredProperties = - Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); - private static final Pattern patternScriptModuleName = - Pattern.compile(" SCRIPT_MODULE_NAME:[ \\t]*(\\S+)\\s*$"); - private static final Pattern patternMixedModuleName = - Pattern.compile(" ((MIXED_)?MODULE_NAME):[ \\t]*(\\S+)\\s*$"); - private static final Pattern patternCommand = - Pattern.compile("^--(([a-z-]+)( .*)?)$"); - - /** - Looks for "directives." If a compatible one is found, it is - processed and this function returns. If an incompatible one is found, - a description of it is returned and processing of the test must - end immediately. - */ - private void checkForDirective(String line) throws IncompatibleDirective { - if(line.startsWith("#")){ - throw new IncompatibleDirective(this, "C-preprocessor input: "+line); - }else if(line.startsWith("---")){ - new IncompatibleDirective(this, "triple-dash: "+line); - } - Matcher m = patternScriptModuleName.matcher(line); - if( m.find() ){ - moduleName = m.group(1); - return; - } - m = patternRequiredProperties.matcher(line); - if( m.find() ){ - throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); - } - m = patternMixedModuleName.matcher(line); - if( m.find() ){ - throw new IncompatibleDirective(this, m.group(1)+": "+m.group(3)); - } - if( line.indexOf("\n|")>=0 ){ - throw new IncompatibleDirective(this, "newline-pipe combination."); - } - return; - } - - boolean isCommandLine(String line, boolean checkForImpl){ - final Matcher m = patternCommand.matcher(line); - boolean rc = m.find(); - if( rc && checkForImpl ){ - rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); - } - return rc; - } - - /** - If line looks like a command, returns an argv for that command - invocation, else returns null. - */ - String[] getCommandArgv(String line){ - final Matcher m = patternCommand.matcher(line); - return m.find() ? m.group(1).trim().split("\\s+") : null; - } - - /** - Fetches lines until the next recognized command. Throws if - checkForDirective() does. Returns null if there is no input or - it's only whitespace. The returned string retains all whitespace. - - Note that "subcommands", --command-like constructs in the body - which do not match a known command name are considered to be - content, not commands. - */ - String fetchCommandBody(){ - final StringBuilder sb = new StringBuilder(); - String line; - while( (null != (line = peekLine())) ){ - checkForDirective(line); - if( !isCommandLine(line, true) ){ - sb.append(line).append("\n"); - consumePeeked(); - }else{ - break; - } - } - line = sb.toString(); - return line.trim().isEmpty() ? null : line; - } - - private void processCommand(SQLTester t, String[] argv) throws Exception{ - verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); - if(outer.getVerbosity()>1){ - final String input = t.getInputText(); - if( !input.isEmpty() ) verbose2("Input buffer = ",input); - } - CommandDispatcher2.dispatch(t, this, argv); - } - - void toss(Object... msg) throws TestScriptFailed { - StringBuilder sb = new StringBuilder(); - for(Object s : msg) sb.append(s); - throw new TestScriptFailed(this, sb.toString()); - } - - /** - Runs this test script in the context of the given tester object. - */ - @SuppressWarnings("unchecked") - public boolean run(SQLTester tester) throws Exception { - reset(); - setVerbosity(tester.getVerbosity()); - String line, directive; - String[] argv; - while( null != (line = getLine()) ){ - //verbose(line); - checkForDirective(line); - argv = getCommandArgv(line); - if( null!=argv ){ - processCommand(tester, argv); - continue; - } - tester.appendInput(line,true); - } - return true; - } -} diff --git a/manifest b/manifest index 83a8306b8c..0714a98a0b 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Defer\sstatic\sJNI-side\sinit\sof\sSQLTester\suntil\smain()\sis\scalled\sso\sthat\sits\sauto-extensions\sdo\snot\sleak\sover\sto\sclients\sof\sthe\smain\slibrary. -D 2023-08-10T01:19:40.795 +C Move\sall\sof\sthe\sSQLTester\scode\sinto\sa\ssingle\sfile,\ssince\sit's\sonly\sgot\s1\spublic\sclass.\sRemove\s'public'\sfrom\smany\smethods\swhich\sdon't\sneed\sit.\sAdd\smore\sdocumentation\sto\sit. +D 2023-08-10T01:44:48.660 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ 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 52f402abb8c4695a58f734d20455cf1a5afaaa10ceacc47bcbf1b06a8d5d27e8 +F ext/jni/GNUmakefile dcaf23ca24ee2ec2d2e36afdc58886fcf147830c25989a669599c69d38796493 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520 F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328 @@ -266,9 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/Outer.java b06acf9c79e8dbc8fea4a98b00724a6a76e3ee4503eb114671d2885f8fb3df8b -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java a5d843ade1f4456cf5cd3d021f5593ff60c0d8786fb55a2d59d7a984738c299e -F ext/jni/src/org/sqlite/jni/tester/TestScript.java 9f172fcffae9935c6d1c9686b3f69df199a191b7280c926bf85fa8fa30ebbddf +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e7148d734ee13baa8a49508a9a7890914d8d57ff8353fce78b60a875f407cf91 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2091,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 52fa6f78414c41073431c166550806bb8a835bd38cfc1236c9363784c78b81b9 -R e4a7dc0518dd6056979335c8c6e73e83 +P e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 +R 4272692fbef77fb1007b0ab4a881bf94 U stephan -Z 125c5fe250744e87fc135cd6f93efe47 +Z 62b15385eb7331c7503b86f0cead2232 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 904da66b33..f0f3317811 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 \ No newline at end of file +2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 \ No newline at end of file From eda67031de4a047eddb3b2feeaf03586018a4325 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 02:09:12 +0000 Subject: [PATCH 123/148] Change the SQLite3Jni API annotations to use SOURCE retention (used only at compile-time). FossilOrigin-Name: 3c3fea6bf284721ac376e2ab5a757cf30245dd39264aaf98a8d6cd5575484275 --- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 7 +++++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 6847b05da3..12d16d9bd3 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -16,6 +16,7 @@ import java.nio.charset.StandardCharsets; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; /** @@ -25,7 +26,8 @@ import java.lang.annotation.ElementType; This annotation is solely for the reader's information. */ -@Retention(RetentionPolicy.RUNTIME) +@Documented +@Retention(RetentionPolicy.SOURCE) @Target(ElementType.PARAMETER) @interface Nullable{} @@ -39,7 +41,8 @@ import java.lang.annotation.ElementType; is in place to programmatically ensure that NotNull is conformed to in client code. */ -@Retention(RetentionPolicy.RUNTIME) +@Documented +@Retention(RetentionPolicy.SOURCE) @Target(ElementType.PARAMETER) @interface NotNull{} diff --git a/manifest b/manifest index 0714a98a0b..b645efb827 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Move\sall\sof\sthe\sSQLTester\scode\sinto\sa\ssingle\sfile,\ssince\sit's\sonly\sgot\s1\spublic\sclass.\sRemove\s'public'\sfrom\smany\smethods\swhich\sdon't\sneed\sit.\sAdd\smore\sdocumentation\sto\sit. -D 2023-08-10T01:44:48.660 +C Change\sthe\sSQLite3Jni\sAPI\sannotations\sto\suse\sSOURCE\sretention\s(used\sonly\sat\scompile-time). +D 2023-08-10T02:09:12.500 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -253,7 +253,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 2fc40765b9f45973103d12d6e9d7df1c9d93afaba7b884a0f16a2fde040c374c +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5f0a9fc79a5c1c1c595f54018dbb8435c8715ff008d68c1834c0c95132178f5d F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P e461fdd53bd3212bee24ec5f5d5c234011ab30f3f67e115de9f85fdb760e3848 -R 4272692fbef77fb1007b0ab4a881bf94 +P 2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 +R c74f8964f3325f83e2be6d2e051bbaac U stephan -Z 62b15385eb7331c7503b86f0cead2232 +Z bd78d11df277c77344c8874eae1308fe # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f0f3317811..ee19d5563d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 \ No newline at end of file +3c3fea6bf284721ac376e2ab5a757cf30245dd39264aaf98a8d6cd5575484275 \ No newline at end of file From f703dfa38101360f982e738e01dcfce6c6fb8e7a Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 04:24:12 +0000 Subject: [PATCH 124/148] Generic cleanups and fixes in SQLTester. FossilOrigin-Name: fc5d3cc30d2b96da42ea10dfb39f1631ff93b8384514fffd641b343df51da2a6 --- ext/jni/GNUmakefile | 2 +- ext/jni/src/c/sqlite3-jni.c | 10 +-- ext/jni/src/c/sqlite3-jni.h | 4 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 4 +- .../src/org/sqlite/jni/tester/SQLTester.java | 65 ++++++++++++++----- manifest | 20 +++--- manifest.uuid | 2 +- 7 files changed, 69 insertions(+), 38 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 8506014462..d081616364 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -224,7 +224,7 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) org.sqlite.jni.Tester1 $(if $(test.flags),-- $(test.flags),) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) -tester.flags ?= --verbose +tester.flags ?= # --verbose .PHONY: tester ifeq (1,$(enable.tester)) tester: $(CLASS_FILES.tester) $(sqlite3-jni.dll) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 4ecd9a980e..51576771de 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -166,7 +166,7 @@ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#jni_interface_functions_and_pointers */ #define JENV_OSELF JNIEnv * const env, jobject jSelf -#define JENV_CSELF JNIEnv * const env, jclass jSelf +#define JENV_CSELF JNIEnv * const env, jclass jKlazz /* Helpers to account for -Xcheck:jni warnings about not having checked for exceptions. */ #define IFTHREW if((*env)->ExceptionCheck(env)) @@ -4203,7 +4203,7 @@ Java_org_sqlite_jni_SQLite3Jni_uncacheJniEnv(JENV_CSELF){ sqlite3.c instead of sqlite3.h. */ JNIEXPORT void JNICALL -Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF, jclass klazzSjni){ +Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ enum JType { JTYPE_INT, JTYPE_BOOL @@ -4266,16 +4266,16 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF, jclass klazzSjni){ for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; - fieldId = (*env)->GetStaticFieldID(env, klazzSjni, pConfFlag->zName, zSig); + fieldId = (*env)->GetStaticFieldID(env, jKlazz, pConfFlag->zName, zSig); EXCEPTION_IS_FATAL("Missing an expected static member of the SQLite3Jni class."); //MARKER(("Setting %s (field=%p) = %d\n", pConfFlag->zName, fieldId, pConfFlag->value)); assert(fieldId); switch(pConfFlag->jtype){ case JTYPE_INT: - (*env)->SetStaticIntField(env, klazzSjni, fieldId, (jint)pConfFlag->value); + (*env)->SetStaticIntField(env, jKlazz, fieldId, (jint)pConfFlag->value); break; case JTYPE_BOOL: - (*env)->SetStaticBooleanField(env, klazzSjni, fieldId, + (*env)->SetStaticBooleanField(env, jKlazz, fieldId, pConfFlag->value ? JNI_TRUE : JNI_FALSE); break; } diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 5a2da91f32..1054be991d 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -758,10 +758,10 @@ extern "C" { /* * Class: org_sqlite_jni_SQLite3Jni * Method: init - * Signature: (Ljava/lang/Class;)V + * Signature: ()V */ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_init - (JNIEnv *, jclass, jclass); + (JNIEnv *, jclass); /* * Class: org_sqlite_jni_SQLite3Jni diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 12d16d9bd3..182515e72f 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -126,7 +126,7 @@ public final class SQLite3Jni { //! Not used private SQLite3Jni(){} //! Called from static init code. - private static native void init(@NotNull Class c); + private static native void init(); /** Each thread which uses the SQLite3 JNI APIs should call @@ -1457,6 +1457,6 @@ public final class SQLite3Jni { static { // This MUST come after the SQLITE_MAX_... values or else // attempting to modify them silently fails. - init(SQLite3Jni.class); + init(); } } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 1e3443b718..276104d4df 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -47,27 +47,56 @@ enum ResultRowMode { NEWLINE }; +/** + Base exception type for test-related failures. +*/ class SQLTesterException extends RuntimeException { - public SQLTesterException(String msg){ + private boolean bFatal = false; + + SQLTesterException(String msg){ super(msg); } + + protected SQLTesterException(String msg, boolean fatal){ + super(msg); + bFatal = fatal; + } + + /** + Indicates whether the framework should consider this exception + type as immediately fatal to the test run or not. + */ + final boolean isFatal(){ return bFatal; } } +/** + Generic test-failed exception. + */ class TestScriptFailed extends SQLTesterException { public TestScriptFailed(TestScript ts, String msg){ - super(ts.getOutputPrefix()+": "+msg); + super(ts.getOutputPrefix()+": "+msg, true); } } +/** + Thrown when an unknown test command is encountered in a script. +*/ class UnknownCommand extends SQLTesterException { public UnknownCommand(TestScript ts, String cmd){ - super(ts.getOutputPrefix()+": unknown command: "+cmd); + super(ts.getOutputPrefix()+": unknown command: "+cmd, false); } } +/** + Thrown when an "incompatible directive" is found in a script. This + can be the presence of a C-preprocessor construct, specific + metadata tags within a test script's header, or specific test + constructs which are incompatible with this particular + implementation. +*/ class IncompatibleDirective extends SQLTesterException { public IncompatibleDirective(TestScript ts, String line){ - super(ts.getOutputPrefix()+": incompatible directive: "+line); + super(ts.getOutputPrefix()+": incompatible directive: "+line, false); } } @@ -124,6 +153,12 @@ class Outer { test-script-interpreter.md. This is a work in progress. + + + An instance of this application provides a core set of services + which TestScript instances use for processing testing logic. + TestScripts, in turn, delegate the concrete test work to Command + objects, which the TestScript parses on their behalf. */ public class SQLTester { //! List of input script files. @@ -205,17 +240,10 @@ public class SQLTester { outln("----->>>>> running [",f,"]"); try{ ts.run(this); - }catch(UnknownCommand e){ - /* currently not fatal */ - outln(e); + }catch(SQLTesterException e){ + outln("EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); ++nAbortedScript; - }catch(IncompatibleDirective e){ - /* not fatal */ - outln(e); - ++nAbortedScript; - }catch(Exception e){ - ++nAbortedScript; - throw e; + if( e.isFatal() ) throw e; }finally{ outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); } @@ -266,7 +294,7 @@ public class SQLTester { SQLTester affirmDbId(int n) throws IndexOutOfBoundsException { if(n<0 || n>=aDb.length){ - throw new IndexOutOfBoundsException("illegal db number."); + throw new IndexOutOfBoundsException("illegal db number: "+n); } return this; } @@ -325,10 +353,13 @@ public class SQLTester { tracking running totals. */ void reset(){ + clearInputBuffer(); + clearResultBuffer(); + closeAllDbs(); nTest = 0; nullView = "nil"; - clearInputBuffer(); - closeAllDbs(); + emitColNames = false; + iCurrentDb = 0; } void setNullValue(String v){nullView = v;} diff --git a/manifest b/manifest index b645efb827..61d721bb1d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Change\sthe\sSQLite3Jni\sAPI\sannotations\sto\suse\sSOURCE\sretention\s(used\sonly\sat\scompile-time). -D 2023-08-10T02:09:12.500 +C Generic\scleanups\sand\sfixes\sin\sSQLTester. +D 2023-08-10T04:24:12.767 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,10 +231,10 @@ 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 dcaf23ca24ee2ec2d2e36afdc58886fcf147830c25989a669599c69d38796493 +F ext/jni/GNUmakefile ce6c7af0a6f0d9ab1502606d191ea7025969b37bc6de67ae93e294ac2ac5d736 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c bae09ff8bf45f19a506a4eaaf693d26b81f0dd0a410b82475e04dde4b1c5a520 -F ext/jni/src/c/sqlite3-jni.h 84a3fc3d308e347a2f6b24e4cb8bbafdfa8e75361302047d788e51a307cb2328 +F ext/jni/src/c/sqlite3-jni.c 3fda1e271054835adec606b4dbaaa6db5ae120b59a72308e4b906739c0a27a87 +F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -253,7 +253,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 5f0a9fc79a5c1c1c595f54018dbb8435c8715ff008d68c1834c0c95132178f5d +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 578fb62013bbffefc0c37afcbf07dddd290b32147fe6f0d995bc55d044c714ce F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e7148d734ee13baa8a49508a9a7890914d8d57ff8353fce78b60a875f407cf91 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 5b940aec0939ef766d11f5123b899248e0a3c05f2dc2bd0df26d07b0964c11c0 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2815d676951abdab674c374fd903486ea5796f8ee4cb338d41f19693419f8471 -R c74f8964f3325f83e2be6d2e051bbaac +P 3c3fea6bf284721ac376e2ab5a757cf30245dd39264aaf98a8d6cd5575484275 +R e0dcea60ec5219f4cbd640eec233c812 U stephan -Z bd78d11df277c77344c8874eae1308fe +Z df3f8d652fff3c111483e31409cde9e4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ee19d5563d..a95933c56d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3c3fea6bf284721ac376e2ab5a757cf30245dd39264aaf98a8d6cd5575484275 \ No newline at end of file +fc5d3cc30d2b96da42ea10dfb39f1631ff93b8384514fffd641b343df51da2a6 \ No newline at end of file From 6a61a19f9aa906ac7620ad08ae60c4380e1a46e8 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 05:14:22 +0000 Subject: [PATCH 125/148] Initial version of REQUIRED_PROPERTIES support for SQLTester, with TEMPSTORE_(FILE/MEM) and RECURSIVE_TRIGGERS options. FossilOrigin-Name: 48d16c9d2fe5f54b09004b4f09759c4e2ad247ae84130feb557951e32f48976a --- ext/jni/GNUmakefile | 2 + .../src/org/sqlite/jni/tester/SQLTester.java | 108 ++++++++++++------ ext/jni/src/tests/000-000-sanity.test | 7 +- manifest | 16 +-- manifest.uuid | 2 +- 5 files changed, 90 insertions(+), 45 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index d081616364..0c522ec8b0 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -236,6 +236,8 @@ tester: @echo "SQLTester support is disabled. Build with enable.tester=1 to enable it." endif +tests: test tester + $(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 diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 276104d4df..34b8cd5adc 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -69,6 +69,12 @@ class SQLTesterException extends RuntimeException { final boolean isFatal(){ return bFatal; } } +class DbException extends SQLTesterException { + protected DbException(sqlite3 db, int rc){ + super("DB error #"+rc+": "+sqlite3_errmsg(db),true); + } +} + /** Generic test-failed exception. */ @@ -110,27 +116,19 @@ class Outer { System.out.print(val); } - static void outln(Object val){ - System.out.println(val); - } - - @SuppressWarnings("unchecked") Outer out(Object... vals){ for(Object v : vals) out(v); return this; } - @SuppressWarnings("unchecked") Outer outln(Object... vals){ out(vals).out("\n"); return this; } - @SuppressWarnings("unchecked") Outer verbose(Object... vals){ if(verbosity>0){ - out("VERBOSE",(verbosity>1 ? "+: " : ": ")); - outln(vals); + out("VERBOSE",(verbosity>1 ? "+: " : ": ")).outln(vals); } return this; } @@ -169,6 +167,8 @@ public class SQLTester { private final StringBuilder inputBuffer = new StringBuilder(); //! Test result buffer. private final StringBuilder resultBuffer = new StringBuilder(); + //! Buffer for REQUIRED_PROPERTIES pragmas. + private final StringBuilder dbInitSql = new StringBuilder(); //! Output representation of SQL NULL. private String nullView = "nil"; //! Total tests run. @@ -205,17 +205,14 @@ public class SQLTester { void outputColumnNames(boolean b){ emitColNames = b; } - @SuppressWarnings("unchecked") void verbose(Object... vals){ outer.verbose(vals); } - @SuppressWarnings("unchecked") void outln(Object... vals){ outer.outln(vals); } - @SuppressWarnings("unchecked") void out(Object... vals){ outer.out(vals); } @@ -276,6 +273,14 @@ public class SQLTester { if(addNL) resultBuffer.append('\n'); } + void appendDbInitSql(String n) throws SQLTesterException { + dbInitSql.append(n).append('\n'); + if( null!=getCurrentDb() ){ + //outln("RUNNING DB INIT CODE: ",n); + execSql(null, true, ResultBufferMode.NONE, null, n); + } + } + String getInputText(){ return inputBuffer.toString(); } String getResultText(){ return resultBuffer.toString(); } @@ -333,11 +338,16 @@ public class SQLTester { final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open_v2(name, out, flags, null); final sqlite3 db = out.getValue(); + if( 0==rc && dbInitSql.length() > 0){ + //outln("RUNNING DB INIT CODE: ",dbInitSql.toString()); + rc = execSql(db, false, ResultBufferMode.NONE, + null, dbInitSql.toString()); + } if( 0!=rc ){ final String msg = sqlite3_errmsg(db); sqlite3_close(db); - Util.toss(SQLTesterException.class, "db open failed with code ", - rc," and message: ",msg); + throw new SQLTesterException("db open failed with code "+ + rc+" and message: "+msg); } return aDb[iCurrentDb] = db; } @@ -355,6 +365,7 @@ public class SQLTester { void reset(){ clearInputBuffer(); clearResultBuffer(); + clearBuffer(dbInitSql); closeAllDbs(); nTest = 0; nullView = "nil"; @@ -440,7 +451,7 @@ public class SQLTester { public int execSql(sqlite3 db, boolean throwOnError, ResultBufferMode appendMode, ResultRowMode lineMode, - String sql) throws Exception { + String sql) throws SQLTesterException { final OutputPointer.Int32 oTail = new OutputPointer.Int32(); final OutputPointer.sqlite3_stmt outStmt = new OutputPointer.sqlite3_stmt(); final byte[] sqlUtf8 = sql.getBytes(StandardCharsets.UTF_8); @@ -465,8 +476,7 @@ public class SQLTester { new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ if( 0!=rc ){ if(throwOnError){ - Util.toss(RuntimeException.class, "db op failed with rc=" - +rc+": "+sqlite3_errmsg(db)); + throw new DbException(db, rc); }else if( null!=sb ){ appendDbErr(db, sb, rc); } @@ -495,7 +505,7 @@ public class SQLTester { sb.append( escapeSqlValue(colName) ); break; default: - Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); } sb.append(' '); } @@ -512,7 +522,7 @@ public class SQLTester { sb.append( escapeSqlValue(val) ); break; default: - Util.toss(RuntimeException.class, "Unhandled ResultBufferMode."); + throw new SQLTesterException("Unhandled ResultBufferMode: "+appendMode); } } if( ResultRowMode.NEWLINE == lineMode ){ @@ -537,8 +547,7 @@ public class SQLTester { sqlite3_finalize(stmt); } if( 0!=rc && throwOnError ){ - Util.toss(RuntimeException.class, "db op failed with rc=" - +rc+": "+sqlite3_errmsg(db)); + throw new DbException(db, rc); } return rc; } @@ -875,7 +884,7 @@ class TableResultCommand extends Command { public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ argcCheck(ts,argv,0); t.incrementTestCounter(); - String body = ts.fetchCommandBody(); + String body = ts.fetchCommandBody(t); if( null==body ) ts.toss("Missing ",argv[0]," body."); body = body.trim(); if( !body.endsWith("\n--end") ){ @@ -1031,13 +1040,12 @@ class TestScript { return "["+(moduleName==null ? filename : moduleName)+"] line "+ cur.lineNo; } - @SuppressWarnings("unchecked") private TestScript verboseN(int level, Object... vals){ final int verbosity = outer.getVerbosity(); if(verbosity>=level){ - outer.out("VERBOSE",(verbosity>1 ? "+ " : " "), - getOutputPrefix(),": ") - .outln(vals); + outer.out( + "VERBOSE", (verbosity>1 ? "+ " : " "), getOutputPrefix(), ": " + ).outln(vals); } return this; } @@ -1045,10 +1053,8 @@ class TestScript { private TestScript verbose1(Object... vals){return verboseN(1,vals);} private TestScript verbose2(Object... vals){return verboseN(2,vals);} - @SuppressWarnings("unchecked") public TestScript warn(Object... vals){ - outer.out("WARNING ", getOutputPrefix(),": ") - .outln(vals); + outer.out("WARNING ", getOutputPrefix(),": ").outln(vals); return this; } @@ -1173,6 +1179,34 @@ class TestScript { cur.lineNo = cur.putbackLineNo; } + private boolean checkRequiredProperties(SQLTester t, String[] props) throws SQLTesterException{ + int nOk = 0; + for(String rp : props){ + verbose1("REQUIRED_PROPERTIES: ",rp); + switch(rp){ + case "RECURSIVE_TRIGGERS": + t.appendDbInitSql("pragma recursive_triggers=on;"); + ++nOk; + break; + case "TEMPSTORE_FILE": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + t.appendDbInitSql("pragma temp_store=1;"); + ++nOk; + break; + case "TEMPSTORE_MEM": + /* This _assumes_ that the lib is built with SQLITE_TEMP_STORE=1 or 2, + which we just happen to know is the case */ + t.appendDbInitSql("pragma temp_store=0;"); + ++nOk; + break; + default: + break; + } + } + return props.length == nOk; + } + private static final Pattern patternRequiredProperties = Pattern.compile(" REQUIRED_PROPERTIES:[ \\t]*(\\S.*)\\s*$"); private static final Pattern patternScriptModuleName = @@ -1188,7 +1222,9 @@ class TestScript { a description of it is returned and processing of the test must end immediately. */ - private void checkForDirective(String line) throws IncompatibleDirective { + private void checkForDirective( + SQLTester tester, String line + ) throws IncompatibleDirective { if(line.startsWith("#")){ throw new IncompatibleDirective(this, "C-preprocessor input: "+line); }else if(line.startsWith("---")){ @@ -1201,7 +1237,10 @@ class TestScript { } m = patternRequiredProperties.matcher(line); if( m.find() ){ - throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+m.group(1)); + final String rp = m.group(1); + if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); + } } m = patternMixedModuleName.matcher(line); if( m.find() ){ @@ -1240,11 +1279,11 @@ class TestScript { which do not match a known command name are considered to be content, not commands. */ - String fetchCommandBody(){ + String fetchCommandBody(SQLTester tester){ final StringBuilder sb = new StringBuilder(); String line; while( (null != (line = peekLine())) ){ - checkForDirective(line); + checkForDirective(tester, line); if( !isCommandLine(line, true) ){ sb.append(line).append("\n"); consumePeeked(); @@ -1274,7 +1313,6 @@ class TestScript { /** Runs this test script in the context of the given tester object. */ - @SuppressWarnings("unchecked") public boolean run(SQLTester tester) throws Exception { reset(); setVerbosity(tester.getVerbosity()); @@ -1282,7 +1320,7 @@ class TestScript { String[] argv; while( null != (line = getLine()) ){ //verbose(line); - checkForDirective(line); + checkForDirective(tester, line); argv = getCommandArgv(line); if( null!=argv ){ processCommand(tester, argv); diff --git a/ext/jni/src/tests/000-000-sanity.test b/ext/jni/src/tests/000-000-sanity.test index 905882dc3b..aa37ae79ee 100644 --- a/ext/jni/src/tests/000-000-sanity.test +++ b/ext/jni/src/tests/000-000-sanity.test @@ -5,10 +5,15 @@ ** xMIXED_MODULE_NAME: mixed-module ** xMODULE_NAME: module-name ** xREQUIRED_PROPERTIES: small fast reliable -** REQUIRED_PROPERTIES: +** REQUIRED_PROPERTIES: RECURSIVE_TRIGGERS +** REQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE ** */ --print starting up 😃 +--testcase required-props +PRAGMA recursive_triggers; +PRAGMA temp_store; +--result 1 1 --close all --oom --db 0 diff --git a/manifest b/manifest index 61d721bb1d..58fe6d30f1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Generic\scleanups\sand\sfixes\sin\sSQLTester. -D 2023-08-10T04:24:12.767 +C Initial\sversion\sof\sREQUIRED_PROPERTIES\ssupport\sfor\sSQLTester,\swith\sTEMPSTORE_(FILE/MEM)\sand\sRECURSIVE_TRIGGERS\soptions. +D 2023-08-10T05:14:22.157 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ 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 ce6c7af0a6f0d9ab1502606d191ea7025969b37bc6de67ae93e294ac2ac5d736 +F ext/jni/GNUmakefile d69b26fb294b7a86a2f838012f4161311c06d607680b86ecdb1334f6f78c165c F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c 3fda1e271054835adec606b4dbaaa6db5ae120b59a72308e4b906739c0a27a87 F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 @@ -266,9 +266,9 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 5b940aec0939ef766d11f5123b899248e0a3c05f2dc2bd0df26d07b0964c11c0 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 6eb2db0465cb3d69f59aadab6d2be0aa8aba3181d1f28aa7e1ca21eb840b554e F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba -F ext/jni/src/tests/000-000-sanity.test de89692155bee1bb35120aced6871dd6562014d0cd7c1dcf173297d8bbc03002 +F ext/jni/src/tests/000-000-sanity.test 35817746f1909cc9af5d3e890ee94a43c47ce47127da9cca7d39b0e132d36c84 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 3c3fea6bf284721ac376e2ab5a757cf30245dd39264aaf98a8d6cd5575484275 -R e0dcea60ec5219f4cbd640eec233c812 +P fc5d3cc30d2b96da42ea10dfb39f1631ff93b8384514fffd641b343df51da2a6 +R 9fb1e3deddfffb0e48b6c7087aa53315 U stephan -Z df3f8d652fff3c111483e31409cde9e4 +Z 7f9ad93c32ab9294dee15aaecfd30d3b # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a95933c56d..44eb2be827 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fc5d3cc30d2b96da42ea10dfb39f1631ff93b8384514fffd641b343df51da2a6 \ No newline at end of file +48d16c9d2fe5f54b09004b4f09759c4e2ad247ae84130feb557951e32f48976a \ No newline at end of file From 63fb588d30ff6a5e79e74dcdf2b1c8e287f730c4 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 05:25:13 +0000 Subject: [PATCH 126/148] Give DbException the option of closing the db to simplify error handling in one case. FossilOrigin-Name: 908c9a44505422a3a15bef3a174d8b931863bc9c74485311a0e62cfec30087bd --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 11 ++++++----- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 34b8cd5adc..99b63c29f6 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -70,8 +70,12 @@ class SQLTesterException extends RuntimeException { } class DbException extends SQLTesterException { - protected DbException(sqlite3 db, int rc){ + DbException(sqlite3 db, int rc, boolean closeDb){ super("DB error #"+rc+": "+sqlite3_errmsg(db),true); + if( closeDb ) sqlite3_close_v2(db); + } + DbException(sqlite3 db, int rc){ + this(db, rc, false); } } @@ -344,10 +348,7 @@ public class SQLTester { null, dbInitSql.toString()); } if( 0!=rc ){ - final String msg = sqlite3_errmsg(db); - sqlite3_close(db); - throw new SQLTesterException("db open failed with code "+ - rc+" and message: "+msg); + throw new DbException(db, rc, true); } return aDb[iCurrentDb] = db; } diff --git a/manifest b/manifest index 58fe6d30f1..d851b7791e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\sversion\sof\sREQUIRED_PROPERTIES\ssupport\sfor\sSQLTester,\swith\sTEMPSTORE_(FILE/MEM)\sand\sRECURSIVE_TRIGGERS\soptions. -D 2023-08-10T05:14:22.157 +C Give\sDbException\sthe\soption\sof\sclosing\sthe\sdb\sto\ssimplify\serror\shandling\sin\sone\scase. +D 2023-08-10T05:25:13.132 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 6eb2db0465cb3d69f59aadab6d2be0aa8aba3181d1f28aa7e1ca21eb840b554e +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 4a0504164c632b817f8fc14f93c455da6b7bcf05fcb0bab2cf28e7fda086d009 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba F ext/jni/src/tests/000-000-sanity.test 35817746f1909cc9af5d3e890ee94a43c47ce47127da9cca7d39b0e132d36c84 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P fc5d3cc30d2b96da42ea10dfb39f1631ff93b8384514fffd641b343df51da2a6 -R 9fb1e3deddfffb0e48b6c7087aa53315 +P 48d16c9d2fe5f54b09004b4f09759c4e2ad247ae84130feb557951e32f48976a +R 76947bac9cc560f2389ffc3d550b2e03 U stephan -Z 7f9ad93c32ab9294dee15aaecfd30d3b +Z 6304bd9479984f92f8657a18da8e93ff # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 44eb2be827..bcd1efb7ec 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -48d16c9d2fe5f54b09004b4f09759c4e2ad247ae84130feb557951e32f48976a \ No newline at end of file +908c9a44505422a3a15bef3a174d8b931863bc9c74485311a0e62cfec30087bd \ No newline at end of file From ec8298fbd139dfa41f129ad1c8ddb8c1b0d8c36e Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 10:34:50 +0000 Subject: [PATCH 127/148] More SQLTester docs. Add --verbosity command to help zoom in on script areas while debugging. Spice up test-start/end output with some emoji. FossilOrigin-Name: 8dd08021496f504c23945ecc2bbe1e4a13109fdd03457ca6269b4cb1cc4cd04c --- .../src/org/sqlite/jni/tester/SQLTester.java | 111 ++++++++++++------ .../jni/tester/test-script-interpreter.md | 7 ++ manifest | 14 +-- manifest.uuid | 2 +- 4 files changed, 89 insertions(+), 45 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 99b63c29f6..74049b3fd8 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -232,13 +232,23 @@ public class SQLTester { openDb(0, initialDbName, true); } + static final String[] startEmoji = { + "🚴", "🏄", "🏇", "🤸", "⛹", "🏊", "⛷", "🧗", "🏋" + }; + static final int nStartEmoji = startEmoji.length; + static int iStartEmoji = 0; + + private static String nextStartEmoji(){ + return startEmoji[iStartEmoji++ % nStartEmoji]; + } + public void runTests() throws Exception { for(String f : listInFiles){ reset(); setupInitialDb(); ++nTestFile; final TestScript ts = new TestScript(f); - outln("----->>>>> running [",f,"]"); + outln(nextStartEmoji(), " starting [",f,"]"); try{ ts.run(this); }catch(SQLTesterException e){ @@ -246,7 +256,7 @@ public class SQLTester { ++nAbortedScript; if( e.isFatal() ) throw e; }finally{ - outln("<<<<<----- ",nTest," test(s) in ",ts.getFilename()); + outln("🏁 ",nTest," test(s) in ",ts.getFilename()); } } Util.unlink(initialDbName); @@ -561,6 +571,7 @@ public class SQLTester { if(a.startsWith("-")){ final String flag = a.replaceFirst("-+",""); if( flag.equals("verbose") ){ + // Use --verbose up to 3 times t.setVerbosity(t.getVerbosity() + 1); }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); @@ -678,7 +689,9 @@ abstract class Command { Must process one command-unit of work and either return (on success) or throw (on error). - The first two arguments specify the context of the test. + The first two arguments specify the context of the test. The TestScript + provides the content of the test and the SQLTester providers the sandbox + in which that script is being evaluated. argv is a list with the command name followed by any arguments to that command. The argcCheck() method from this class provides @@ -854,7 +867,7 @@ class ResultCommand extends Command { final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; if( !result.equals(sArgs) ){ t.outln(argv[0]," FAILED comparison. Result buffer:\n", - result,"\nargs:\n",sArgs); + result,"\nExpected result:\n",sArgs); ts.toss(argv[0]+" comparison failed."); } } @@ -934,7 +947,15 @@ class TestCaseCommand extends Command { } } -class CommandDispatcher2 { +//! --verbosity command +class VerbosityCommand extends Command { + public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ + argcCheck(ts,argv,1); + ts.setVerbosity( Integer.parseInt(argv[1]) ); + } +} + +class CommandDispatcher { private static java.util.Map commandMap = new java.util.HashMap<>(); @@ -947,22 +968,23 @@ class CommandDispatcher2 { Command rv = commandMap.get(name); if( null!=rv ) return rv; switch(name){ - case "close": rv = new CloseDbCommand(); break; - case "column-names":rv = new ColumnNamesCommand(); break; - case "db": rv = new DbCommand(); break; - case "glob": rv = new GlobCommand(); break; - case "json": rv = new JsonCommand(); break; - case "json-block": rv = new JsonBlockCommand(); break; - case "new": rv = new NewDbCommand(); break; - case "notglob": rv = new NotGlobCommand(); break; - case "null": rv = new NullCommand(); break; - case "oom": rv = new NoopCommand(); break; - case "open": rv = new OpenDbCommand(); break; - case "print": rv = new PrintCommand(); break; - case "result": rv = new ResultCommand(); break; - case "run": rv = new RunCommand(); break; - case "tableresult": rv = new TableResultCommand(); break; - case "testcase": rv = new TestCaseCommand(); break; + case "close": rv = new CloseDbCommand(); break; + case "column-names": rv = new ColumnNamesCommand(); break; + case "db": rv = new DbCommand(); break; + case "glob": rv = new GlobCommand(); break; + case "json": rv = new JsonCommand(); break; + case "json-block": rv = new JsonBlockCommand(); break; + case "new": rv = new NewDbCommand(); break; + case "notglob": rv = new NotGlobCommand(); break; + case "null": rv = new NullCommand(); break; + case "oom": rv = new NoopCommand(); break; + case "open": rv = new OpenDbCommand(); break; + case "print": rv = new PrintCommand(); break; + case "result": rv = new ResultCommand(); break; + case "run": rv = new RunCommand(); break; + case "tableresult": rv = new TableResultCommand(); break; + case "testcase": rv = new TestCaseCommand(); break; + case "verbosity": rv = new VerbosityCommand(); break; default: rv = null; break; } if( null!=rv ) commandMap.put(name, rv); @@ -990,24 +1012,38 @@ class CommandDispatcher2 { evaluation are delegated elsewhere. */ class TestScript { + //! input file private String filename = null; + //! Name pulled from the SCRIPT_MODULE_NAME directive of the file private String moduleName = null; + //! Content buffer state. private final Cursor cur = new Cursor(); + //! Utility for console output. private final Outer outer = new Outer(); + //! File content and parse state. private static final class Cursor { private final StringBuilder sb = new StringBuilder(); byte[] src = null; + //! Current position in this.src. int pos = 0; - int putbackPos = 0; - int putbackLineNo = 0; + //! Current line number. Starts at 0 for internal reasons and will + // line up with 1-based reality once parsing starts. int lineNo = 0 /* yes, zero */; + //! Putback value for this.pos. + int putbackPos = 0; + //! Putback line number + int putbackLineNo = 0; + //! Peeked-to pos, used by peekLine() and consumePeeked(). int peekedPos = 0; + //! Peeked-to line number. int peekedLineNo = 0; - boolean inComment = false; - void reset(){ - sb.setLength(0); pos = 0; lineNo = 0/*yes, zero*/; inComment = false; + //! Restore parsing state to the start of the stream. + void rewind(){ + sb.setLength(0); + pos = lineNo = putbackPos = putbackLineNo = peekedPos = peekedLineNo = 0 + /* kinda missing memset() about now. */; } } @@ -1033,6 +1069,10 @@ class TestScript { return moduleName; } + /** + Verbosity level 0 produces no debug/verbose output. Level 1 produces + some and level 2 produces more. + */ public void setVerbosity(int level){ outer.setVerbosity(level); } @@ -1041,6 +1081,8 @@ class TestScript { return "["+(moduleName==null ? filename : moduleName)+"] line "+ cur.lineNo; } + + //! Output vals only if level<=current verbosity level. private TestScript verboseN(int level, Object... vals){ final int verbosity = outer.getVerbosity(); if(verbosity>=level){ @@ -1053,17 +1095,12 @@ class TestScript { private TestScript verbose1(Object... vals){return verboseN(1,vals);} private TestScript verbose2(Object... vals){return verboseN(2,vals);} - - public TestScript warn(Object... vals){ - outer.out("WARNING ", getOutputPrefix(),": ").outln(vals); - return this; - } + private TestScript verbose3(Object... vals){return verboseN(3,vals);} private void reset(){ - cur.reset(); + cur.rewind(); } - /** Returns the next line from the buffer, minus the trailing EOL. @@ -1257,7 +1294,7 @@ class TestScript { final Matcher m = patternCommand.matcher(line); boolean rc = m.find(); if( rc && checkForImpl ){ - rc = null!=CommandDispatcher2.getCommandByName(m.group(2)); + rc = null!=CommandDispatcher.getCommandByName(m.group(2)); } return rc; } @@ -1300,9 +1337,9 @@ class TestScript { verbose1("running command: ",argv[0], " ", Util.argvToString(argv)); if(outer.getVerbosity()>1){ final String input = t.getInputText(); - if( !input.isEmpty() ) verbose2("Input buffer = ",input); + if( !input.isEmpty() ) verbose3("Input buffer = ",input); } - CommandDispatcher2.dispatch(t, this, argv); + CommandDispatcher.dispatch(t, this, argv); } void toss(Object... msg) throws TestScriptFailed { @@ -1320,7 +1357,7 @@ class TestScript { String line, directive; String[] argv; while( null != (line = getLine()) ){ - //verbose(line); + verbose3("input line: ",line); checkForDirective(tester, line); argv = getCommandArgv(line); if( null!=argv ){ diff --git a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md index 95220ed59c..a51d64d107 100644 --- a/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md +++ b/ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md @@ -260,3 +260,10 @@ is always an exact strcmp() not a GLOB. The --print command emits both its arguments and its body (if any) to stdout, indenting each line of output. + +### The --column-names command + +The --column-names command requires 0 or 1 as an argument, to disable +resp. enable it, and modifies SQL execution to include column names +in output. When this option is on, each column value emitted gets +prefixed by its column name, with a single space between them. diff --git a/manifest b/manifest index d851b7791e..a5e0b71b17 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Give\sDbException\sthe\soption\sof\sclosing\sthe\sdb\sto\ssimplify\serror\shandling\sin\sone\scase. -D 2023-08-10T05:25:13.132 +C More\sSQLTester\sdocs.\sAdd\s--verbosity\scommand\sto\shelp\szoom\sin\son\sscript\sareas\swhile\sdebugging.\sSpice\sup\stest-start/end\soutput\swith\ssome\semoji. +D 2023-08-10T10:34:50.418 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,8 +266,8 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 4a0504164c632b817f8fc14f93c455da6b7bcf05fcb0bab2cf28e7fda086d009 -F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md ab7169b08566a082ef55c9ef8a553827f99958ed3e076f31eef757563fae51ba +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java bdc96c5c8a94e49a9e4af6774da1f7373062c9a9f854e0236feaf338552f3018 +F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test 35817746f1909cc9af5d3e890ee94a43c47ce47127da9cca7d39b0e132d36c84 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 48d16c9d2fe5f54b09004b4f09759c4e2ad247ae84130feb557951e32f48976a -R 76947bac9cc560f2389ffc3d550b2e03 +P 908c9a44505422a3a15bef3a174d8b931863bc9c74485311a0e62cfec30087bd +R 723877b1e83c903a6eebac5debe5b713 U stephan -Z 6304bd9479984f92f8657a18da8e93ff +Z 9ac434387da17df208fe2a5e4745ca4c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bcd1efb7ec..97025e7b30 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -908c9a44505422a3a15bef3a174d8b931863bc9c74485311a0e62cfec30087bd \ No newline at end of file +8dd08021496f504c23945ecc2bbe1e4a13109fdd03457ca6269b4cb1cc4cd04c \ No newline at end of file From 34fac74362c82dbcdd89ac758083f6b3f4d5d947 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 10:44:53 +0000 Subject: [PATCH 128/148] Add the current --testcase name to SQLTester --verbose output. FossilOrigin-Name: f87367402b25adf30f35ab75aa5efc495230d4a83f2fc10b99734c3f3f593840 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 12 +++++++++--- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 74049b3fd8..f9d60b715f 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -941,7 +941,7 @@ class TableResultCommand extends Command { class TestCaseCommand extends Command { public void process(SQLTester t, TestScript ts, String[] argv) throws Exception{ argcCheck(ts,argv,1); - // TODO?: do something with the test name + ts.setTestCaseName(argv[1]); t.clearResultBuffer(); t.clearInputBuffer(); } @@ -1016,6 +1016,8 @@ class TestScript { private String filename = null; //! Name pulled from the SCRIPT_MODULE_NAME directive of the file private String moduleName = null; + //! Current test case name. + private String testCaseName = null; //! Content buffer state. private final Cursor cur = new Cursor(); //! Utility for console output. @@ -1078,8 +1080,9 @@ class TestScript { } public String getOutputPrefix(){ - return "["+(moduleName==null ? filename : moduleName)+"] line "+ - cur.lineNo; + String rc = "["+(moduleName==null ? filename : moduleName)+"]"; + if( null!=testCaseName ) rc += "["+testCaseName+"]"; + return rc + " line "+ cur.lineNo; } //! Output vals only if level<=current verbosity level. @@ -1098,9 +1101,12 @@ class TestScript { private TestScript verbose3(Object... vals){return verboseN(3,vals);} private void reset(){ + testCaseName = null; cur.rewind(); } + void setTestCaseName(String n){ testCaseName = n; } + /** Returns the next line from the buffer, minus the trailing EOL. diff --git a/manifest b/manifest index a5e0b71b17..be35dea7b4 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sSQLTester\sdocs.\sAdd\s--verbosity\scommand\sto\shelp\szoom\sin\son\sscript\sareas\swhile\sdebugging.\sSpice\sup\stest-start/end\soutput\swith\ssome\semoji. -D 2023-08-10T10:34:50.418 +C Add\sthe\scurrent\s--testcase\sname\sto\sSQLTester\s--verbose\soutput. +D 2023-08-10T10:44:53.161 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java bdc96c5c8a94e49a9e4af6774da1f7373062c9a9f854e0236feaf338552f3018 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java a53136bc757eb46fe5d404d93ffef5fa16bd60957f40b446a320eded81ea8295 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test 35817746f1909cc9af5d3e890ee94a43c47ce47127da9cca7d39b0e132d36c84 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 908c9a44505422a3a15bef3a174d8b931863bc9c74485311a0e62cfec30087bd -R 723877b1e83c903a6eebac5debe5b713 +P 8dd08021496f504c23945ecc2bbe1e4a13109fdd03457ca6269b4cb1cc4cd04c +R 6945d572bb09d06283928530ca319652 U stephan -Z 9ac434387da17df208fe2a5e4745ca4c +Z 7ed984402281dd637927941ca5072ed3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 97025e7b30..772c64c19e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8dd08021496f504c23945ecc2bbe1e4a13109fdd03457ca6269b4cb1cc4cd04c \ No newline at end of file +f87367402b25adf30f35ab75aa5efc495230d4a83f2fc10b99734c3f3f593840 \ No newline at end of file From 0a8cc1f6342952a54b37e35897ed3c87bbacc89b Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 10:58:55 +0000 Subject: [PATCH 129/148] Add a visual indicator (emoji) to TestScript verbose messages, dependent on the message's level of verbosity. FossilOrigin-Name: dc323d3894f2d53470cd8be261632267fa3d2af73500acfa1e9adbfa53b771fd --- .../src/org/sqlite/jni/tester/SQLTester.java | 20 +++++++++---------- manifest | 12 +++++------ manifest.uuid | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index f9d60b715f..685d89b039 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -780,7 +780,7 @@ class GlobCommand extends Command { ResultRowMode.ONELINE, sql); final String result = t.getResultText(); final String sArgs = Util.argvToString(argv); - //t.verbose(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); + //t2.verbose2(argv[0]," rc = ",rc," result buffer:\n", result,"\nargs:\n",sArgs); final String glob = Util.argvToString(argv); rc = SQLTester.strglob(glob, result); if( (negate && 0==rc) || (!negate && 0!=rc) ){ @@ -861,7 +861,7 @@ class ResultCommand extends Command { argcCheck(ts,argv,0,-1); t.incrementTestCounter(); final String sql = t.takeInputBuffer(); - //t.verbose(argv[0]," SQL =\n",sql); + //ts.verbose2(argv[0]," SQL =\n",sql); int rc = t.execSql(null, false, bufferMode, ResultRowMode.ONELINE, sql); final String result = t.getResultText().trim(); final String sArgs = argv.length>1 ? Util.argvToString(argv) : ""; @@ -884,8 +884,8 @@ class RunCommand extends Command { ResultRowMode.ONELINE, sql); if( 0!=rc && t.isVerbose() ){ String msg = sqlite3_errmsg(db); - t.verbose(argv[0]," non-fatal command error #",rc,": ", - msg,"\nfor SQL:\n",sql); + ts.verbose1(argv[0]," non-fatal command error #",rc,": ", + msg,"\nfor SQL:\n",sql); } } } @@ -924,7 +924,7 @@ class TableResultCommand extends Command { } for(int i = 0; i < res.length; ++i){ final String glob = globs[i].replaceAll("\\s+"," ").trim(); - //t.verbose(argv[0]," <<",glob,">> vs <<",res[i],">>"); + //ts.verbose2(argv[0]," <<",glob,">> vs <<",res[i],">>"); if( jsonMode ){ if( !glob.equals(res[i]) ){ ts.toss(argv[0], " json <<",glob, ">> does not match: <<", @@ -1085,20 +1085,20 @@ class TestScript { return rc + " line "+ cur.lineNo; } + static final String[] verboseLabel = {"🔈",/*"🔉",*/"🔊","📢"}; //! Output vals only if level<=current verbosity level. private TestScript verboseN(int level, Object... vals){ final int verbosity = outer.getVerbosity(); if(verbosity>=level){ - outer.out( - "VERBOSE", (verbosity>1 ? "+ " : " "), getOutputPrefix(), ": " + outer.out( verboseLabel[level-1], getOutputPrefix(), " ",level,": " ).outln(vals); } return this; } - private TestScript verbose1(Object... vals){return verboseN(1,vals);} - private TestScript verbose2(Object... vals){return verboseN(2,vals);} - private TestScript verbose3(Object... vals){return verboseN(3,vals);} + TestScript verbose1(Object... vals){return verboseN(1,vals);} + TestScript verbose2(Object... vals){return verboseN(2,vals);} + TestScript verbose3(Object... vals){return verboseN(3,vals);} private void reset(){ testCaseName = null; diff --git a/manifest b/manifest index be35dea7b4..3fdf78c78f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\scurrent\s--testcase\sname\sto\sSQLTester\s--verbose\soutput. -D 2023-08-10T10:44:53.161 +C Add\sa\svisual\sindicator\s(emoji)\sto\sTestScript\sverbose\smessages,\sdependent\son\sthe\smessage's\slevel\sof\sverbosity. +D 2023-08-10T10:58:55.662 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java a53136bc757eb46fe5d404d93ffef5fa16bd60957f40b446a320eded81ea8295 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 8f547b7c1c440b6fae4546308a93d9c81f42165743a011ba27aee44b487ae19e F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test 35817746f1909cc9af5d3e890ee94a43c47ce47127da9cca7d39b0e132d36c84 F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 8dd08021496f504c23945ecc2bbe1e4a13109fdd03457ca6269b4cb1cc4cd04c -R 6945d572bb09d06283928530ca319652 +P f87367402b25adf30f35ab75aa5efc495230d4a83f2fc10b99734c3f3f593840 +R a7620ac844ee78dbe35574ca68ee4232 U stephan -Z 7ed984402281dd637927941ca5072ed3 +Z 9d16676db12b9f3723b82b6122641cb8 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 772c64c19e..f4a29c6de0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f87367402b25adf30f35ab75aa5efc495230d4a83f2fc10b99734c3f3f593840 \ No newline at end of file +dc323d3894f2d53470cd8be261632267fa3d2af73500acfa1e9adbfa53b771fd \ No newline at end of file From e51fae413416af09609f3ad93deecb7d5266e147 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 11:04:46 +0000 Subject: [PATCH 130/148] Disable REQUIRED_PROPERTIES handling in SQLTester, per /chat. Scripts with that directive are now skipped. FossilOrigin-Name: ddc534cb25b59faf18a860a51f2dd41a1a73963aeb541b9553301fe784608393 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 6 +++--- ext/jni/src/tests/000-000-sanity.test | 8 ++------ manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 685d89b039..42b278effb 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -1282,9 +1282,9 @@ class TestScript { m = patternRequiredProperties.matcher(line); if( m.find() ){ final String rp = m.group(1); - if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ - throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); - } + //if( ! checkRequiredProperties( tester, rp.split("\\s+") ) ){ + throw new IncompatibleDirective(this, "REQUIRED_PROPERTIES: "+rp); + //} } m = patternMixedModuleName.matcher(line); if( m.find() ){ diff --git a/ext/jni/src/tests/000-000-sanity.test b/ext/jni/src/tests/000-000-sanity.test index aa37ae79ee..2bfacb1cec 100644 --- a/ext/jni/src/tests/000-000-sanity.test +++ b/ext/jni/src/tests/000-000-sanity.test @@ -5,15 +5,11 @@ ** xMIXED_MODULE_NAME: mixed-module ** xMODULE_NAME: module-name ** xREQUIRED_PROPERTIES: small fast reliable -** REQUIRED_PROPERTIES: RECURSIVE_TRIGGERS -** REQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE +** xREQUIRED_PROPERTIES: RECURSIVE_TRIGGERS +** xREQUIRED_PROPERTIES: TEMPSTORE_MEM TEMPSTORE_FILE ** */ --print starting up 😃 ---testcase required-props -PRAGMA recursive_triggers; -PRAGMA temp_store; ---result 1 1 --close all --oom --db 0 diff --git a/manifest b/manifest index 3fdf78c78f..461a032437 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\svisual\sindicator\s(emoji)\sto\sTestScript\sverbose\smessages,\sdependent\son\sthe\smessage's\slevel\sof\sverbosity. -D 2023-08-10T10:58:55.662 +C Disable\sREQUIRED_PROPERTIES\shandling\sin\sSQLTester,\sper\s/chat.\sScripts\swith\sthat\sdirective\sare\snow\sskipped. +D 2023-08-10T11:04:46.863 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,9 +266,9 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 8f547b7c1c440b6fae4546308a93d9c81f42165743a011ba27aee44b487ae19e +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 87d0b6299d982d25c371196196fc6b05633356e81f739e952e2f31e979952a6e F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e -F ext/jni/src/tests/000-000-sanity.test 35817746f1909cc9af5d3e890ee94a43c47ce47127da9cca7d39b0e132d36c84 +F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 F ext/lsm1/Makefile a553b728bba6c11201b795188c5708915cc4290f02b7df6ba7e8c4c943fd5cd9 F ext/lsm1/Makefile.msc f8c878b467232226de288da320e1ac71c131f5ec91e08b21f502303347260013 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f87367402b25adf30f35ab75aa5efc495230d4a83f2fc10b99734c3f3f593840 -R a7620ac844ee78dbe35574ca68ee4232 +P dc323d3894f2d53470cd8be261632267fa3d2af73500acfa1e9adbfa53b771fd +R 95f7fef6f77e0863ccb7ed7a7cc786a0 U stephan -Z 9d16676db12b9f3723b82b6122641cb8 +Z 0b7b070da9a52d86376d81fa24fc7cc4 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index f4a29c6de0..0ba905e7be 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dc323d3894f2d53470cd8be261632267fa3d2af73500acfa1e9adbfa53b771fd \ No newline at end of file +ddc534cb25b59faf18a860a51f2dd41a1a73963aeb541b9553301fe784608393 \ No newline at end of file From 61186d1243325e1a0c058be8f1feef0721293b46 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 11:15:20 +0000 Subject: [PATCH 131/148] Make test completion status more visible at a glance on modern terminals. FossilOrigin-Name: a4e96c306c4c270f417243e7923d7e6c4f860528dd67990dfd8d9768a6c4873f --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 6 ++++-- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 42b278effb..6930b32a10 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -249,14 +249,16 @@ public class SQLTester { ++nTestFile; final TestScript ts = new TestScript(f); outln(nextStartEmoji(), " starting [",f,"]"); + boolean threw = false; try{ ts.run(this); }catch(SQLTesterException e){ - outln("EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); + threw = true; + outln("❗EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); ++nAbortedScript; if( e.isFatal() ) throw e; }finally{ - outln("🏁 ",nTest," test(s) in ",ts.getFilename()); + outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",ts.getFilename()); } } Util.unlink(initialDbName); diff --git a/manifest b/manifest index 461a032437..31949ac767 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Disable\sREQUIRED_PROPERTIES\shandling\sin\sSQLTester,\sper\s/chat.\sScripts\swith\sthat\sdirective\sare\snow\sskipped. -D 2023-08-10T11:04:46.863 +C Make\stest\scompletion\sstatus\smore\svisible\sat\sa\sglance\son\smodern\sterminals. +D 2023-08-10T11:15:20.741 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 87d0b6299d982d25c371196196fc6b05633356e81f739e952e2f31e979952a6e +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 24a1099dc1dde1508de44172b2438ba8f585bbc591fe2389171ce8647663a2b2 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P dc323d3894f2d53470cd8be261632267fa3d2af73500acfa1e9adbfa53b771fd -R 95f7fef6f77e0863ccb7ed7a7cc786a0 +P ddc534cb25b59faf18a860a51f2dd41a1a73963aeb541b9553301fe784608393 +R 86c85827eee124ab2195331beb10cb02 U stephan -Z 0b7b070da9a52d86376d81fa24fc7cc4 +Z a074cfea0973f3e7d868cadec52b5a31 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0ba905e7be..15a49cb7ed 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ddc534cb25b59faf18a860a51f2dd41a1a73963aeb541b9553301fe784608393 \ No newline at end of file +a4e96c306c4c270f417243e7923d7e6c4f860528dd67990dfd8d9768a6c4873f \ No newline at end of file From 152801470332bccbde78d0f086e761287716e9d1 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 12:36:40 +0000 Subject: [PATCH 132/148] Resolve two assertions in the auto-extension JNI which were triggered via new SQLTester infrastructure. Move SQLTester's db-init SQL injection into an auto-extension. FossilOrigin-Name: 2952906c30bc2b7987f2c39837d56bd121f5817dc094e6ccdb6d4eea5e9b8d17 --- ext/jni/src/c/sqlite3-jni.c | 31 +++++++++---- ext/jni/src/org/sqlite/jni/AutoExtension.java | 2 + ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 35 ++++++++------- .../src/org/sqlite/jni/tester/SQLTester.java | 44 ++++++++++++++----- manifest | 18 ++++---- manifest.uuid | 2 +- 6 files changed, 86 insertions(+), 46 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 51576771de..0987d5f846 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1877,13 +1877,14 @@ WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) #if S3JNI_ENABLE_AUTOEXT /* Central auto-extension handler. */ +FIXME_THREADING static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; int rc; JNIEnv * env = 0; S3JniDb * const ps = S3JniGlobal.autoExt.psOpening; - + //MARKER(("auto-extension on open()ing ps@%p db@%p\n", ps, pDb)); S3JniGlobal.autoExt.psOpening = 0; if( !pAX ){ assert( 0==S3JniGlobal.autoExt.isRunning ); @@ -2089,6 +2090,7 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ assert(version == 1 || version == 2); ps = S3JniDb_for_db(env, jDb, 0, 0); if(ps){ + //MARKER(("close()ing db@%p\n", ps->pDb)); rc = 1==version ? (jint)sqlite3_close(ps->pDb) : (jint)sqlite3_close_v2(ps->pDb); S3JniDb_set_aside(ps) /* MUST come after close() because of ps->trace. */; @@ -2566,6 +2568,7 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, S3JniGlobal.autoExt.psOpening = *ps; } #endif + //MARKER(("pre-open ps@%p\n", *ps)); return *ps ? 0 : SQLITE_NOMEM; } @@ -2582,14 +2585,17 @@ static int s3jni_open_pre(JNIEnv * const env, S3JniEnvCache **jc, */ static int s3jni_open_post(JNIEnv * const env, S3JniDb * ps, sqlite3 **ppDb, jobject jOut, int theRc){ + //MARKER(("post-open() ps@%p db@%p\n", ps, *ppDb)); #if S3JNI_ENABLE_AUTOEXT - assert( S3JniGlobal.autoExt.pHead ? 0==S3JniGlobal.autoExt.psOpening : 1 ); + assert( S3JniGlobal.autoExt.pHead ? ps!=S3JniGlobal.autoExt.psOpening : 1 ); S3JniGlobal.autoExt.psOpening = 0; #endif if(*ppDb){ assert(ps->jDb); #if S3JNI_ENABLE_AUTOEXT - assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); + //MARKER(("*autoExt.pHead=%p, ppDb=%p, ps->pDb=%p\n", S3JniGlobal.autoExt.pHead, *ppDb, ps->pDb)); + // invalid when an autoext triggers another open(): + // assert( S3JniGlobal.autoExt.pHead ? *ppDb==ps->pDb : 0==ps->pDb ); #endif ps->pDb = *ppDb; NativePointerHolder_set(env, ps->jDb, *ppDb, S3JniClassNames.sqlite3); @@ -2607,13 +2613,17 @@ JDECL(jint,1open)(JENV_CSELF, jstring strName, jobject jOut){ jobject jDb = 0; S3JniDb * ps = 0; S3JniEnvCache * jc = 0; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); - if( rc ) return rc; - rc = sqlite3_open(zName, &pOut); - //MARKER(("env=%p, *env=%p\n", env, *env)); - rc = s3jni_open_post(env, ps, &pOut, jOut, rc); - assert(rc==0 ? pOut!=0 : 1); - sqlite3_free(zName); + if( 0==rc ){ + rc = sqlite3_open(zName, &pOut); + //MARKER(("env=%p, *env=%p\n", env, *env)); + //MARKER(("open() ps@%p db@%p\n", ps, pOut)); + rc = s3jni_open_post(env, ps, &pOut, jOut, rc); + assert(rc==0 ? pOut!=0 : 1); + sqlite3_free(zName); + } + S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } @@ -2625,6 +2635,7 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, S3JniDb * ps = 0; S3JniEnvCache * jc = 0; char *zVfs = 0; + S3JniDb * const prevOpening = S3JniGlobal.autoExt.psOpening; int rc = s3jni_open_pre(env, &jc, strName, &zName, &ps, &jDb); if( 0==rc && strVfs ){ zVfs = s3jni_jstring_to_utf8(jc, strVfs, 0); @@ -2635,12 +2646,14 @@ JDECL(jint,1open_1v2)(JENV_CSELF, jstring strName, if( 0==rc ){ rc = sqlite3_open_v2(zName, &pOut, (int)flags, zVfs); } + //MARKER(("open_v2() ps@%p db@%p\n", ps, pOut)); /*MARKER(("zName=%s, zVfs=%s, pOut=%p, flags=%d, nrc=%d\n", zName, zVfs, pOut, (int)flags, nrc));*/ rc = s3jni_open_post(env, ps, &pOut, jOut, rc); assert(rc==0 ? pOut!=0 : 1); sqlite3_free(zName); sqlite3_free(zVfs); + S3JniGlobal.autoExt.psOpening = prevOpening; return (jint)rc; } diff --git a/ext/jni/src/org/sqlite/jni/AutoExtension.java b/ext/jni/src/org/sqlite/jni/AutoExtension.java index 3a58b6589f..a2ab6a0f75 100644 --- a/ext/jni/src/org/sqlite/jni/AutoExtension.java +++ b/ext/jni/src/org/sqlite/jni/AutoExtension.java @@ -24,6 +24,8 @@ public interface AutoExtension { As an exception (as it were) to the callbacks-must-not-throw rule, AutoExtensions may do so and the exception's error message will be set as the db's error string. + + Results are undefined if db is closed by an auto-extension. */ int xEntryPoint(sqlite3 db); } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 182515e72f..3ffb771ec7 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -188,22 +188,25 @@ public final class SQLite3Jni { */ public static synchronized native int sqlite3_auto_extension(@NotNull AutoExtension callback); - public static int sqlite3_bind_blob(@NotNull sqlite3_stmt stmt, int ndx, - @Nullable byte[] data){ + public static int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ return (null == data) ? sqlite3_bind_null(stmt, ndx) : sqlite3_bind_blob(stmt, ndx, data, data.length); } - private static native int sqlite3_bind_blob(@NotNull sqlite3_stmt stmt, - int ndx, @Nullable byte[] data, - int n); + private static native int sqlite3_bind_blob( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int n + ); - public static native int sqlite3_bind_double(@NotNull sqlite3_stmt stmt, - int ndx, double v); + public static native int sqlite3_bind_double( + @NotNull sqlite3_stmt stmt, int ndx, double v + ); - public static native int sqlite3_bind_int(@NotNull sqlite3_stmt stmt, - int ndx, int v); + public static native int sqlite3_bind_int( + @NotNull sqlite3_stmt stmt, int ndx, int v + ); public static native int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, int ndx, long v); @@ -257,10 +260,10 @@ public final class SQLite3Jni { to clear the busy handler. Calling this multiple times with the same object is a no-op on the second and subsequent calls. */ - public static native int sqlite3_busy_handler(@NotNull sqlite3 db, + public static native synchronized int sqlite3_busy_handler(@NotNull sqlite3 db, @Nullable BusyHandler handler); - public static native int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms); + public static native synchronized int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms); /** Works like the C API except that it returns false, without side @@ -489,12 +492,12 @@ public final class SQLite3Jni { or sqlite3_open_v2() so that they have a predictible object to pass to, e.g., the sqlite3_collation_needed() callback. */ - public static native int sqlite3_open(@Nullable String filename, - @NotNull OutputPointer.sqlite3 ppDb); + public static native synchronized int sqlite3_open(@Nullable String filename, + @NotNull OutputPointer.sqlite3 ppDb); - public static native int sqlite3_open_v2(@Nullable String filename, - @NotNull OutputPointer.sqlite3 ppDb, - int flags, @Nullable String zVfs); + public static native synchronized int sqlite3_open_v2(@Nullable String filename, + @NotNull OutputPointer.sqlite3 ppDb, + int flags, @Nullable String zVfs); /** The sqlite3_prepare() family of functions require slightly diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 6930b32a10..eabbb4580b 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -227,7 +227,9 @@ public class SQLTester { //verbose("Added file ",filename); } - private void setupInitialDb() throws Exception { + private void setupInitialDb() throws DbException { + outln("setupInitialDb()"); + closeDb(0); Util.unlink(initialDbName); openDb(0, initialDbName, true); } @@ -245,7 +247,6 @@ public class SQLTester { public void runTests() throws Exception { for(String f : listInFiles){ reset(); - setupInitialDb(); ++nTestFile; final TestScript ts = new TestScript(f); outln(nextStartEmoji(), " starting [",f,"]"); @@ -289,13 +290,14 @@ public class SQLTester { if(addNL) resultBuffer.append('\n'); } - void appendDbInitSql(String n) throws SQLTesterException { + void appendDbInitSql(String n) throws DbException { dbInitSql.append(n).append('\n'); if( null!=getCurrentDb() ){ //outln("RUNNING DB INIT CODE: ",n); execSql(null, true, ResultBufferMode.NONE, null, n); } } + String getDbInitSql(){ return dbInitSql.toString(); } String getInputText(){ return inputBuffer.toString(); } @@ -330,7 +332,7 @@ public class SQLTester { return affirmDbId(id).aDb[id]; } - void closeDb(int id) throws Exception{ + void closeDb(int id) { final sqlite3 db = affirmDbId(id).aDb[id]; if( null != db ){ sqlite3_close_v2(db); @@ -338,7 +340,7 @@ public class SQLTester { } } - void closeDb() throws Exception { closeDb(iCurrentDb); } + void closeDb() { closeDb(iCurrentDb); } void closeAllDbs(){ for(int i = 0; i 0 ){ t.outln("Aborted ",t.nAbortedScript," script(s)."); diff --git a/manifest b/manifest index 31949ac767..8ea514fc24 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Make\stest\scompletion\sstatus\smore\svisible\sat\sa\sglance\son\smodern\sterminals. -D 2023-08-10T11:15:20.741 +C Resolve\stwo\sassertions\sin\sthe\sauto-extension\sJNI\swhich\swere\striggered\svia\snew\sSQLTester\sinfrastructure.\sMove\sSQLTester's\sdb-init\sSQL\sinjection\sinto\san\sauto-extension. +D 2023-08-10T12:36:40.522 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,10 +233,10 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile d69b26fb294b7a86a2f838012f4161311c06d607680b86ecdb1334f6f78c165c F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 3fda1e271054835adec606b4dbaaa6db5ae120b59a72308e4b906739c0a27a87 +F ext/jni/src/c/sqlite3-jni.c 12ac735b074d681694ecdbb27d99c66273ea21b8f1047b659b34581bd129a118 F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 -F ext/jni/src/org/sqlite/jni/AutoExtension.java 3409ad8954d6466bf772e6be9379e0e337312b446b668287062845755a16844d +F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c F ext/jni/src/org/sqlite/jni/Collation.java 8dffbb00938007ad0967b2ab424d3c908413af1bbd3d212b9c9899910f1218d1 F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7c48e2a98042fcf6cacf14d42444037eab8 @@ -253,7 +253,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 578fb62013bbffefc0c37afcbf07dddd290b32147fe6f0d995bc55d044c714ce +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1291facf4e681a740c5cc8086ffa8a47d2b73f1faf660dab29c3527d6fba70f1 F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 24a1099dc1dde1508de44172b2438ba8f585bbc591fe2389171ce8647663a2b2 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e0b6c9c24176f6541baded998f7dda4328937d18c894f6d0bff36dec0c279ff2 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ddc534cb25b59faf18a860a51f2dd41a1a73963aeb541b9553301fe784608393 -R 86c85827eee124ab2195331beb10cb02 +P a4e96c306c4c270f417243e7923d7e6c4f860528dd67990dfd8d9768a6c4873f +R e78d561aec5cdd8a539cc739fbc7cee6 U stephan -Z a074cfea0973f3e7d868cadec52b5a31 +Z e3784a9c8c2c97335fd71d5cd2d6fc99 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 15a49cb7ed..8136499772 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a4e96c306c4c270f417243e7923d7e6c4f860528dd67990dfd8d9768a6c4873f \ No newline at end of file +2952906c30bc2b7987f2c39837d56bd121f5817dc094e6ccdb6d4eea5e9b8d17 \ No newline at end of file From 92f699d3843d23ea00b12b278905742d6c1917b4 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 13:10:25 +0000 Subject: [PATCH 133/148] Document that auto-extensions registered via JNI will fail if they open a db (to avoid triggering an endless loop in the auto-extensions). Discover the hard way that JDKv19 creates different mangled JNI names for some functions than JDKv8. Start reformatting much of the JNI API decls in prep for making them even longer. FossilOrigin-Name: 99c0941f1c006622932a9cca12661f354f363a6c8a2b5675ea66149e0a9eb927 --- ext/jni/src/c/sqlite3-jni.c | 15 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 572 +++++++++++------- .../src/org/sqlite/jni/tester/SQLTester.java | 12 +- manifest | 16 +- manifest.uuid | 2 +- 5 files changed, 391 insertions(+), 226 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 0987d5f846..cac9734480 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1889,11 +1889,14 @@ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, if( !pAX ){ assert( 0==S3JniGlobal.autoExt.isRunning ); return 0; - }else if( S3JniGlobal.autoExt.isRunning ){ + } + else if( S3JniGlobal.autoExt.isRunning ){ + /* Necessary to avoid certain endless loop/stack overflow cases. */ *pzErr = sqlite3_mprintf("Auto-extensions must not be triggered while " "auto-extensions are running."); return SQLITE_MISUSE; - }else if(!ps){ + } + else if(!ps){ MARKER(("Internal error: cannot find S3JniDb for auto-extension\n")); return SQLITE_ERROR; }else if( (*S3JniGlobal.jvm)->GetEnv(S3JniGlobal.jvm, (void **)&env, JNI_VERSION_1_8) ){ @@ -1905,7 +1908,7 @@ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, ps->pDb = pDb; assert( ps->jDb ); NativePointerHolder_set(env, ps->jDb, pDb, S3JniClassNames.sqlite3); - S3JniGlobal.autoExt.isRunning = 1; + ++S3JniGlobal.autoExt.isRunning; for( ; pAX; pAX = pAX->pNext ){ rc = (*env)->CallIntMethod(env, pAX->jObj, pAX->midFunc, ps->jDb); IFTHREW { @@ -1922,7 +1925,7 @@ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, break; } } - S3JniGlobal.autoExt.isRunning = 0; + --S3JniGlobal.autoExt.isRunning; return rc; } @@ -2348,6 +2351,10 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( } /* sqlite3_db_config() for (int,int*) */ +/* ACHTUNG: openjdk v19 creates a different mangled name for this + function than openjdk v8 does. It is not yet know when that + incompatibility was introduced, so we cannot yet reliably #if it + here. */ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2)( JENV_CSELF, jobject jDb, jint op, jobject jOut ){ diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 3ffb771ec7..945dd80ad9 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -174,12 +174,19 @@ public final class SQLite3Jni { exceptions: - The callback interface is more limited because of - cross-language differences. + cross-language differences. Specifically, auto-extensions do + not have access to the sqlite3_api object which native + auto-extensions do. - - All of the auto extension routines will fail without side + - If an auto-extension opens a db, thereby triggering recursion + in the auto-extension handler, it will fail with a message + explaining that recursion is not permitted. + + - All of the other auto extension routines will fail without side effects if invoked from within the execution of an - auto-extension. i.e. auto extensions can neither be - added, removed, nor cleared while one is running. + auto-extension. i.e. auto extensions can neither be added, + removed, nor cleared while one registered with this function is + running. See the AutoExtension class docs for more information. @@ -208,35 +215,44 @@ public final class SQLite3Jni { @NotNull sqlite3_stmt stmt, int ndx, int v ); - public static native int sqlite3_bind_int64(@NotNull sqlite3_stmt stmt, - int ndx, long v); + public static native int sqlite3_bind_int64( + @NotNull sqlite3_stmt stmt, int ndx, long v + ); - public static native int sqlite3_bind_null(@NotNull sqlite3_stmt stmt, int ndx); + public static native int sqlite3_bind_null( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native int sqlite3_bind_parameter_count(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_bind_parameter_count( + @NotNull sqlite3_stmt stmt + ); /** A level of indirection required to ensure that the input to the C-level function of the same name is a NUL-terminated UTF-8 string. */ - private static native int sqlite3_bind_parameter_index(@NotNull sqlite3_stmt stmt, - byte[] paramName); + private static native int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, byte[] paramName + ); - public static int sqlite3_bind_parameter_index(@NotNull sqlite3_stmt stmt, - @NotNull String paramName){ + public static int sqlite3_bind_parameter_index( + @NotNull sqlite3_stmt stmt, @NotNull String paramName + ){ final byte[] utf8 = (paramName+"\0").getBytes(StandardCharsets.UTF_8); return sqlite3_bind_parameter_index(stmt, utf8); } - public static int sqlite3_bind_text(@NotNull sqlite3_stmt stmt, int ndx, - @Nullable String data){ + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable String data + ){ if(null == data) return sqlite3_bind_null(stmt, ndx); final byte[] utf8 = data.getBytes(StandardCharsets.UTF_8); return sqlite3_bind_text(stmt, ndx, utf8, utf8.length); } - public static int sqlite3_bind_text(@NotNull sqlite3_stmt stmt, int ndx, - @Nullable byte[] data){ + public static int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data + ){ return (null == data) ? sqlite3_bind_null(stmt, ndx) : sqlite3_bind_text(stmt, ndx, data, data.length); @@ -247,12 +263,17 @@ public final class SQLite3Jni { SQLITE_TRANSIENT for the final parameter and (B) behaves like sqlite3_bind_null() if the data argument is null. */ - private static native int sqlite3_bind_text(@NotNull sqlite3_stmt stmt, int ndx, - @Nullable byte[] data, int maxBytes); + private static native int sqlite3_bind_text( + @NotNull sqlite3_stmt stmt, int ndx, @Nullable byte[] data, int maxBytes + ); - public static native int sqlite3_bind_zeroblob(@NotNull sqlite3_stmt stmt, int ndx, int n); + public static native int sqlite3_bind_zeroblob( + @NotNull sqlite3_stmt stmt, int ndx, int n + ); - public static native int sqlite3_bind_zeroblob64(@NotNull sqlite3_stmt stmt, int ndx, long n); + public static native int sqlite3_bind_zeroblob64( + @NotNull sqlite3_stmt stmt, int ndx, long n + ); /** As for the C-level function of the same name, with a BusyHandler @@ -260,50 +281,84 @@ public final class SQLite3Jni { to clear the busy handler. Calling this multiple times with the same object is a no-op on the second and subsequent calls. */ - public static native synchronized int sqlite3_busy_handler(@NotNull sqlite3 db, - @Nullable BusyHandler handler); + public static native synchronized int sqlite3_busy_handler( + @NotNull sqlite3 db, @Nullable BusyHandler handler + ); - public static native synchronized int sqlite3_busy_timeout(@NotNull sqlite3 db, int ms); + public static native synchronized int sqlite3_busy_timeout( + @NotNull sqlite3 db, int ms + ); /** Works like the C API except that it returns false, without side effects, if auto extensions are currently running. */ - public static synchronized native boolean sqlite3_cancel_auto_extension(@NotNull AutoExtension ax); + public static synchronized native boolean sqlite3_cancel_auto_extension( + @NotNull AutoExtension ax + ); - public static native int sqlite3_changes(@NotNull sqlite3 db); + public static native int sqlite3_changes( + @NotNull sqlite3 db + ); - public static native long sqlite3_changes64(@NotNull sqlite3 db); + public static native long sqlite3_changes64( + @NotNull sqlite3 db + ); - public static native int sqlite3_clear_bindings(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_clear_bindings( + @NotNull sqlite3_stmt stmt + ); - public static native int sqlite3_close(@NotNull sqlite3 db); + public static native int sqlite3_close( + @NotNull sqlite3 db + ); - public static native int sqlite3_close_v2(@NotNull sqlite3 db); + public static native int sqlite3_close_v2( + @NotNull sqlite3 db + ); - public static native byte[] sqlite3_column_blob(@NotNull sqlite3_stmt stmt, int ndx); + public static native byte[] sqlite3_column_blob( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native int sqlite3_column_bytes(@NotNull sqlite3_stmt stmt, int ndx); + public static native int sqlite3_column_bytes( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native int sqlite3_column_bytes16(@NotNull sqlite3_stmt stmt, int ndx); + public static native int sqlite3_column_bytes16( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native int sqlite3_column_count(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_column_count( + @NotNull sqlite3_stmt stmt + ); - public static native double sqlite3_column_double(@NotNull sqlite3_stmt stmt, int ndx); + public static native double sqlite3_column_double( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native int sqlite3_column_int(@NotNull sqlite3_stmt stmt, int ndx); + public static native int sqlite3_column_int( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native long sqlite3_column_int64(@NotNull sqlite3_stmt stmt, int ndx); + public static native long sqlite3_column_int64( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native String sqlite3_column_name(@NotNull sqlite3_stmt stmt, int ndx); + public static native String sqlite3_column_name( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native String sqlite3_column_database_name(@NotNull sqlite3_stmt stmt, int ndx); + public static native String sqlite3_column_database_name( + @NotNull sqlite3_stmt stmt, int ndx + ); /** Column counterpart of sqlite3_value_java_object(). */ - public static Object sqlite3_column_java_object(@NotNull sqlite3_stmt stmt, - int ndx){ + public static Object sqlite3_column_java_object( + @NotNull sqlite3_stmt stmt, int ndx + ){ Object rv = null; sqlite3_value v = sqlite3_column_value(stmt, ndx); if(null!=v){ @@ -320,30 +375,37 @@ public final class SQLite3Jni { Column counterpart of sqlite3_value_java_casted(). */ @SuppressWarnings("unchecked") - public static T sqlite3_column_java_casted(@NotNull sqlite3_stmt stmt, - int ndx, - @NotNull Class type){ + public static T sqlite3_column_java_casted( + @NotNull sqlite3_stmt stmt, int ndx, @NotNull Class type + ){ final Object o = sqlite3_column_java_object(stmt, ndx); return type.isInstance(o) ? (T)o : null; } - public static native String sqlite3_column_origin_name(@NotNull sqlite3_stmt stmt, int ndx); + public static native String sqlite3_column_origin_name( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native String sqlite3_column_table_name(@NotNull sqlite3_stmt stmt, int ndx); + public static native String sqlite3_column_table_name( + @NotNull sqlite3_stmt stmt, int ndx + ); /** To extract _standard_ UTF-8, use sqlite3_column_text(). This API includes no functions for working with Java's Modified UTF-8. */ - public static native String sqlite3_column_text16(@NotNull sqlite3_stmt stmt, int ndx); + public static native String sqlite3_column_text16( + @NotNull sqlite3_stmt stmt, int ndx + ); /** Returns the given column's contents as UTF-8-encoded (not MUTF-8) text. Use sqlite3_column_text16() to fetch the text */ - public static native byte[] sqlite3_column_text(@NotNull sqlite3_stmt stmt, - int ndx); + public static native byte[] sqlite3_column_text( + @NotNull sqlite3_stmt stmt, int ndx + ); // The real utility of this function is questionable. // /** @@ -382,35 +444,46 @@ public final class SQLite3Jni { // return rv; // } - public static native int sqlite3_column_type(@NotNull sqlite3_stmt stmt, - int ndx); + public static native int sqlite3_column_type( + @NotNull sqlite3_stmt stmt, int ndx + ); - public static native sqlite3_value sqlite3_column_value(@NotNull sqlite3_stmt stmt, - int ndx); + public static native sqlite3_value sqlite3_column_value( + @NotNull sqlite3_stmt stmt, int ndx + ); /** This functions like C's sqlite3_collation_needed16() because Java's string type is compatible with that interface. */ - public static native int sqlite3_collation_needed(@NotNull sqlite3 db, - @Nullable CollationNeeded callback); + public static native int sqlite3_collation_needed( + @NotNull sqlite3 db, @Nullable CollationNeeded callback + ); /** Returns the db handle passed to sqlite3_open() or sqlite3_open_v2(), as opposed to a new wrapper object. */ - public static native sqlite3 sqlite3_context_db_handle(@NotNull sqlite3_context cx); + public static native sqlite3 sqlite3_context_db_handle( + @NotNull sqlite3_context cx + ); - public static native CommitHook sqlite3_commit_hook(@NotNull sqlite3 db, @Nullable CommitHook hook); + public static native CommitHook sqlite3_commit_hook( + @NotNull sqlite3 db, @Nullable CommitHook hook + ); - public static native String sqlite3_compileoption_get(int n); + public static native String sqlite3_compileoption_get( + int n + ); - public static native boolean sqlite3_compileoption_used(@NotNull String optName); + public static native boolean sqlite3_compileoption_used( + @NotNull String optName + ); - public static native int sqlite3_create_collation(@NotNull sqlite3 db, - @NotNull String name, - int eTextRep, - @NotNull Collation col); + public static native int sqlite3_create_collation( + @NotNull sqlite3 db, @NotNull String name, int eTextRep, + @NotNull Collation col + ); /** The Java counterpart to the C-native sqlite3_create_function(), @@ -420,23 +493,27 @@ public final class SQLite3Jni { SQLFunction's inner classes (Scalar, Aggregate, and Window) for details. */ - public static native int sqlite3_create_function(@NotNull sqlite3 db, - @NotNull String functionName, - int nArg, int eTextRep, - @NotNull SQLFunction func); + public static native int sqlite3_create_function( + @NotNull sqlite3 db, @NotNull String functionName, + int nArg, int eTextRep, @NotNull SQLFunction func + ); - public static native int sqlite3_data_count(@NotNull sqlite3_stmt stmt); + public static native int sqlite3_data_count( + @NotNull sqlite3_stmt stmt + ); - public static native String sqlite3_db_filename(@NotNull sqlite3 db, - @NotNull String dbName); + public static native String sqlite3_db_filename( + @NotNull sqlite3 db, @NotNull String dbName + ); /** Overload for sqlite3_db_config() calls which take (int,int*) variadic arguments. Returns SQLITE_MISUSE if op is not one of the SQLITE_DBCONFIG_... options which uses this call form. */ - public static native int sqlite3_db_config(@NotNull sqlite3 db, int op, - @Nullable OutputPointer.Int32 out); + public static native int sqlite3_db_config( + @NotNull sqlite3 db, int op, @Nullable OutputPointer.Int32 out + ); /** Overload for sqlite3_db_config() calls which take (int,const char*) variadic arguments. As of SQLite3 v3.43 the only such @@ -444,15 +521,17 @@ public final class SQLite3Jni { is not SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be extended in future versions. */ - public static native int sqlite3_db_config(@NotNull sqlite3 db, int op, - @NotNull String mainDbName); + public static native int sqlite3_db_config( + @NotNull sqlite3 db, int op, @NotNull String mainDbName + ); public static native int sqlite3_errcode(@NotNull sqlite3 db); public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); - public static native boolean sqlite3_extended_result_codes(@NotNull sqlite3 db, - boolean onoff); + public static native boolean sqlite3_extended_result_codes( + @NotNull sqlite3 db, boolean onoff + ); public static native String sqlite3_errmsg(@NotNull sqlite3 db); @@ -492,12 +571,14 @@ public final class SQLite3Jni { or sqlite3_open_v2() so that they have a predictible object to pass to, e.g., the sqlite3_collation_needed() callback. */ - public static native synchronized int sqlite3_open(@Nullable String filename, - @NotNull OutputPointer.sqlite3 ppDb); + public static native synchronized int sqlite3_open( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb + ); - public static native synchronized int sqlite3_open_v2(@Nullable String filename, - @NotNull OutputPointer.sqlite3 ppDb, - int flags, @Nullable String zVfs); + public static native synchronized int sqlite3_open_v2( + @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, + int flags, @Nullable String zVfs + ); /** The sqlite3_prepare() family of functions require slightly @@ -518,84 +599,96 @@ public final class SQLite3Jni { necessary, however, and overloads are provided which gloss over that. */ - private static native int sqlite3_prepare(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, int maxBytes, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset); + private static native int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); - public static int sqlite3_prepare(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset){ + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); } - public static int sqlite3_prepare(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt){ + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ return sqlite3_prepare(db, sqlUtf8, sqlUtf8.length, outStmt, null); } - public static int sqlite3_prepare(@NotNull sqlite3 db, @NotNull String sql, - @NotNull OutputPointer.sqlite3_stmt outStmt){ + public static int sqlite3_prepare( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); } - private static native int sqlite3_prepare_v2(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, - int maxBytes, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset); + private static native int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); - public static int sqlite3_prepare_v2(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset){ + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, pTailOffset); } - public static int sqlite3_prepare_v2(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, - @NotNull OutputPointer.sqlite3_stmt outStmt){ + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ return sqlite3_prepare_v2(db, sqlUtf8, sqlUtf8.length, outStmt, null); } - public static int sqlite3_prepare_v2(@NotNull sqlite3 db, - @NotNull String sql, - @NotNull OutputPointer.sqlite3_stmt outStmt){ + public static int sqlite3_prepare_v2( + @NotNull sqlite3 db, @NotNull String sql, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); } - private static native int sqlite3_prepare_v3(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, - int maxBytes, int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset); + private static native int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, + int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ); - public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull byte[] sqlUtf8, - int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt, - @Nullable OutputPointer.Int32 pTailOffset){ + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt, + @Nullable OutputPointer.Int32 pTailOffset + ){ return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, pTailOffset); } - public static int sqlite3_prepare_v3(@NotNull sqlite3 db, - @NotNull byte[] sqlUtf8, - int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt){ + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ return sqlite3_prepare_v3(db, sqlUtf8, sqlUtf8.length, prepFlags, outStmt, null); } - public static int sqlite3_prepare_v3(@NotNull sqlite3 db, @NotNull String sql, - int prepFlags, - @NotNull OutputPointer.sqlite3_stmt outStmt){ + public static int sqlite3_prepare_v3( + @NotNull sqlite3 db, @NotNull String sql, int prepFlags, + @NotNull OutputPointer.sqlite3_stmt outStmt + ){ final byte[] utf8 = sql.getBytes(StandardCharsets.UTF_8); return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } - public static native void sqlite3_progress_handler(@NotNull sqlite3 db, int n, - @Nullable ProgressHandler h); + public static native void sqlite3_progress_handler( + @NotNull sqlite3 db, int n, @Nullable ProgressHandler h + ); //TODO??? void *sqlite3_preupdate_hook(...) and friends @@ -607,7 +700,9 @@ public final class SQLite3Jni { */ public static synchronized native void sqlite3_reset_auto_extension(); - public static native void sqlite3_result_double(@NotNull sqlite3_context cx, double v); + public static native void sqlite3_result_double( + @NotNull sqlite3_context cx, double v + ); /** The main sqlite3_result_error() impl of which all others are @@ -616,53 +711,72 @@ public final class SQLite3Jni { results in the C-level sqlite3_result_error() being called with a complaint about the invalid argument. */ - private static native void sqlite3_result_error(@NotNull sqlite3_context cx, - @Nullable byte[] msg, - int eTextRep); + private static native void sqlite3_result_error( + @NotNull sqlite3_context cx, @Nullable byte[] msg, + int eTextRep + ); - public static void sqlite3_result_error(@NotNull sqlite3_context cx, - @NotNull byte[] utf8){ + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull byte[] utf8 + ){ sqlite3_result_error(cx, utf8, SQLITE_UTF8); } - public static void sqlite3_result_error(@NotNull sqlite3_context cx, - @NotNull String msg){ + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull String msg + ){ final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_8); sqlite3_result_error(cx, utf8, SQLITE_UTF8); } - public static void sqlite3_result_error16(@NotNull sqlite3_context cx, - @Nullable byte[] utf16){ + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @Nullable byte[] utf16 + ){ sqlite3_result_error(cx, utf16, SQLITE_UTF16); } - public static void sqlite3_result_error16(@NotNull sqlite3_context cx, - @NotNull String msg){ + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull String msg + ){ final byte[] utf8 = (msg+"\0").getBytes(StandardCharsets.UTF_16); sqlite3_result_error(cx, utf8, SQLITE_UTF16); } - public static void sqlite3_result_error(@NotNull sqlite3_context cx, - @NotNull Exception e){ + public static void sqlite3_result_error( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ sqlite3_result_error(cx, e.getMessage()); } - public static void sqlite3_result_error16(@NotNull sqlite3_context cx, - @NotNull Exception e){ + public static void sqlite3_result_error16( + @NotNull sqlite3_context cx, @NotNull Exception e + ){ sqlite3_result_error16(cx, e.getMessage()); } - public static native void sqlite3_result_error_toobig(@NotNull sqlite3_context cx); + public static native void sqlite3_result_error_toobig( + @NotNull sqlite3_context cx + ); - public static native void sqlite3_result_error_nomem(@NotNull sqlite3_context cx); + public static native void sqlite3_result_error_nomem( + @NotNull sqlite3_context cx + ); - public static native void sqlite3_result_error_code(@NotNull sqlite3_context cx, int c); + public static native void sqlite3_result_error_code( + @NotNull sqlite3_context cx, int c + ); - public static native void sqlite3_result_null(@NotNull sqlite3_context cx); + public static native void sqlite3_result_null( + @NotNull sqlite3_context cx + ); - public static native void sqlite3_result_int(@NotNull sqlite3_context cx, int v); + public static native void sqlite3_result_int( + @NotNull sqlite3_context cx, int v + ); - public static native void sqlite3_result_int64(@NotNull sqlite3_context cx, long v); + public static native void sqlite3_result_int64( + @NotNull sqlite3_context cx, long v + ); /** Binds the SQL result to the given object, or @@ -680,64 +794,83 @@ public final class SQLite3Jni { Note that there is no sqlite3_bind_java_object() counterpart. */ - public static native void sqlite3_result_java_object(@NotNull sqlite3_context cx, - @NotNull Object o); + public static native void sqlite3_result_java_object( + @NotNull sqlite3_context cx, @NotNull Object o + ); - public static void sqlite3_result_set(@NotNull sqlite3_context cx, - @NotNull Integer v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Integer v + ){ sqlite3_result_int(cx, v); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, int v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, int v + ){ sqlite3_result_int(cx, v); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, - @NotNull Boolean v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Boolean v + ){ sqlite3_result_int(cx, v ? 1 : 0); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, boolean v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, boolean v + ){ sqlite3_result_int(cx, v ? 1 : 0); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, - @NotNull Long v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Long v + ){ sqlite3_result_int64(cx, v); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, long v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, long v + ){ sqlite3_result_int64(cx, v); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, - @NotNull Double v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @NotNull Double v + ){ sqlite3_result_double(cx, v); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, double v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, double v + ){ sqlite3_result_double(cx, v); } - public static void sqlite3_result_set(@NotNull sqlite3_context cx, - @Nullable String v){ + public static void sqlite3_result_set( + @NotNull sqlite3_context cx, @Nullable String v + ){ sqlite3_result_text(cx, v); } - public static native void sqlite3_result_value(@NotNull sqlite3_context cx, - @NotNull sqlite3_value v); + public static native void sqlite3_result_value( + @NotNull sqlite3_context cx, @NotNull sqlite3_value v + ); - public static native void sqlite3_result_zeroblob(@NotNull sqlite3_context cx, - int n); + public static native void sqlite3_result_zeroblob( + @NotNull sqlite3_context cx, int n + ); - public static native int sqlite3_result_zeroblob64(@NotNull sqlite3_context cx, - long n); + public static native int sqlite3_result_zeroblob64( + @NotNull sqlite3_context cx, long n + ); - private static native void sqlite3_result_blob(@NotNull sqlite3_context cx, - @Nullable byte[] blob, - int maxLen); + private static native void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen + ); - public static void sqlite3_result_blob(@NotNull sqlite3_context cx, @Nullable byte[] blob){ + public static void sqlite3_result_blob( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ sqlite3_result_blob(cx, blob, (int)(null==blob ? 0 : blob.length)); } @@ -751,25 +884,29 @@ public final class SQLite3Jni { If maxLen is larger than blob.length, it is truncated to that value. If it is negative, results are undefined. */ - private static native void sqlite3_result_blob64(@NotNull sqlite3_context cx, - @Nullable byte[] blob, - long maxLen); + private static native void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen + ); - public static void sqlite3_result_blob64(@NotNull sqlite3_context cx, - @Nullable byte[] blob){ + public static void sqlite3_result_blob64( + @NotNull sqlite3_context cx, @Nullable byte[] blob + ){ sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); } - private static native void sqlite3_result_text(@NotNull sqlite3_context cx, - @Nullable byte[] text, int maxLen); + private static native void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen + ); - public static void sqlite3_result_text(@NotNull sqlite3_context cx, - @Nullable byte[] text){ + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ sqlite3_result_text(cx, text, null==text ? 0 : text.length); } - public static void sqlite3_result_text(@NotNull sqlite3_context cx, - @Nullable String text){ + public static void sqlite3_result_text( + @NotNull sqlite3_context cx, @Nullable String text + ){ if(null == text) sqlite3_result_null(cx); else{ final byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); @@ -791,21 +928,24 @@ public final class SQLite3Jni { text.length, it is silently truncated to text.length. If it is negative, results are undefined. */ - private static native void sqlite3_result_text64(@NotNull sqlite3_context cx, - @Nullable byte[] text, - long maxLength, int encoding); + private static native void sqlite3_result_text64( + @NotNull sqlite3_context cx, @Nullable byte[] text, + long maxLength, int encoding + ); /** Sets the current UDF result to the given bytes, which are assumed be encoded in UTF-16 using the platform's byte order. */ - public static void sqlite3_result_text16(@NotNull sqlite3_context cx, - @Nullable byte[] text){ + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16); } - public static void sqlite3_result_text16(@NotNull sqlite3_context cx, - @Nullable String text){ + public static void sqlite3_result_text16( + @NotNull sqlite3_context cx, @Nullable String text + ){ if(null == text) sqlite3_result_null(cx); else{ final byte[] b = text.getBytes(StandardCharsets.UTF_16); @@ -817,8 +957,9 @@ public final class SQLite3Jni { Sets the current UDF result to the given bytes, which are assumed be encoded in UTF-16LE. */ - public static void sqlite3_result_text16le(@NotNull sqlite3_context cx, - @Nullable String text){ + public static void sqlite3_result_text16le( + @NotNull sqlite3_context cx, @Nullable String text + ){ if(null == text) sqlite3_result_null(cx); else{ final byte[] b = text.getBytes(StandardCharsets.UTF_16LE); @@ -830,25 +971,31 @@ public final class SQLite3Jni { Sets the current UDF result to the given bytes, which are assumed be encoded in UTF-16BE. */ - public static void sqlite3_result_text16be(@NotNull sqlite3_context cx, - @Nullable byte[] text){ + public static void sqlite3_result_text16be( + @NotNull sqlite3_context cx, @Nullable byte[] text + ){ sqlite3_result_text64(cx, text, text.length, SQLITE_UTF16BE); } - public static void sqlite3_result_text16be(@NotNull sqlite3_context cx, - @NotNull String text){ + public static void sqlite3_result_text16be( + @NotNull sqlite3_context cx, @NotNull String text + ){ final byte[] b = text.getBytes(StandardCharsets.UTF_16BE); sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); } - public static native RollbackHook sqlite3_rollback_hook(@NotNull sqlite3 db, - @Nullable RollbackHook hook); + public static native RollbackHook sqlite3_rollback_hook( + @NotNull sqlite3 db, @Nullable RollbackHook hook + ); //! Sets or unsets (if auth is null) the current authorizer. - public static native int sqlite3_set_authorizer(@NotNull sqlite3 db, - @Nullable Authorizer auth); + public static native int sqlite3_set_authorizer( + @NotNull sqlite3 db, @Nullable Authorizer auth + ); - public static native void sqlite3_set_last_insert_rowid(@NotNull sqlite3 db, long rowid); + public static native void sqlite3_set_last_insert_rowid( + @NotNull sqlite3 db, long rowid + ); public static native int sqlite3_sleep(int ms); @@ -864,7 +1011,9 @@ public final class SQLite3Jni { @NotNull byte[] glob, @NotNull byte[] txt ); - public static int sqlite3_strglob(@NotNull String glob, @NotNull String txt){ + public static int sqlite3_strglob( + @NotNull String glob, @NotNull String txt + ){ return sqlite3_strglob( (glob+"\0").getBytes(StandardCharsets.UTF_8), (txt+"\0").getBytes(StandardCharsets.UTF_8) @@ -905,10 +1054,13 @@ public final class SQLite3Jni { mapping state fails and SQLITE_ERROR if the given callback object cannot be processed propertly (i.e. an internal error). */ - public static native int sqlite3_trace_v2(@NotNull sqlite3 db, int traceMask, - @Nullable Tracer tracer); + public static native int sqlite3_trace_v2( + @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer + ); - public static native UpdateHook sqlite3_update_hook(sqlite3 db, UpdateHook hook); + public static native UpdateHook sqlite3_update_hook( + sqlite3 db, UpdateHook hook + ); public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); @@ -918,7 +1070,9 @@ public final class SQLite3Jni { public static native double sqlite3_value_double(@NotNull sqlite3_value v); - public static native sqlite3_value sqlite3_value_dupe(@NotNull sqlite3_value v); + public static native sqlite3_value sqlite3_value_dupe( + @NotNull sqlite3_value v + ); public static native int sqlite3_value_encoding(@NotNull sqlite3_value v); @@ -935,7 +1089,9 @@ public final class SQLite3Jni { It is up to the caller to inspect the object to determine its type, and cast it if necessary. */ - public static native Object sqlite3_value_java_object(@NotNull sqlite3_value v); + public static native Object sqlite3_value_java_object( + @NotNull sqlite3_value v + ); /** A variant of sqlite3_value_java_object() which returns the diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index eabbb4580b..9448ab5d09 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -228,10 +228,13 @@ public class SQLTester { } private void setupInitialDb() throws DbException { - outln("setupInitialDb()"); - closeDb(0); - Util.unlink(initialDbName); - openDb(0, initialDbName, true); + if( null==aDb[0] ){ + Util.unlink(initialDbName); + openDb(0, initialDbName, true); + }else{ + outln("WARNING: setupInitialDb() unexpectedly ", + "triggered while it is opened."); + } } static final String[] startEmoji = { @@ -594,7 +597,6 @@ public class SQLTester { final AutoExtension ax = new AutoExtension() { private final SQLTester tester = t; public int xEntryPoint(sqlite3 db){ - tester.outln("AutoExtension running db init code on ",db); final String init = tester.getDbInitSql(); if( !init.isEmpty() ){ tester.execSql(db, true, ResultBufferMode.NONE, null, init); diff --git a/manifest b/manifest index 8ea514fc24..2ead815455 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Resolve\stwo\sassertions\sin\sthe\sauto-extension\sJNI\swhich\swere\striggered\svia\snew\sSQLTester\sinfrastructure.\sMove\sSQLTester's\sdb-init\sSQL\sinjection\sinto\san\sauto-extension. -D 2023-08-10T12:36:40.522 +C Document\sthat\sauto-extensions\sregistered\svia\sJNI\swill\sfail\sif\sthey\sopen\sa\sdb\s(to\savoid\striggering\san\sendless\sloop\sin\sthe\sauto-extensions).\sDiscover\sthe\shard\sway\sthat\sJDKv19\screates\sdifferent\smangled\sJNI\snames\sfor\ssome\sfunctions\sthan\sJDKv8.\sStart\sreformatting\smuch\sof\sthe\sJNI\sAPI\sdecls\sin\sprep\sfor\smaking\sthem\seven\slonger. +D 2023-08-10T13:10:25.060 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -233,7 +233,7 @@ F ext/icu/icu.c c074519b46baa484bb5396c7e01e051034da8884bad1a1cb7f09bbe6be3f0282 F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a32075a8 F ext/jni/GNUmakefile d69b26fb294b7a86a2f838012f4161311c06d607680b86ecdb1334f6f78c165c F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d -F ext/jni/src/c/sqlite3-jni.c 12ac735b074d681694ecdbb27d99c66273ea21b8f1047b659b34581bd129a118 +F ext/jni/src/c/sqlite3-jni.c fa251d2033d2210a88ac6190db923f76681be609b97f840360318ab366f3cbdd F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -253,7 +253,7 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 1291facf4e681a740c5cc8086ffa8a47d2b73f1faf660dab29c3527d6fba70f1 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java ca91d7fdd334989ce0514a612878e329cdced5d3697d2357f938c3cf1a68e54d F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java e0b6c9c24176f6541baded998f7dda4328937d18c894f6d0bff36dec0c279ff2 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f5b5ffdfeb6da35556b76f25694bdd862e67f101a88fe35cafb0b7f0af667067 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P a4e96c306c4c270f417243e7923d7e6c4f860528dd67990dfd8d9768a6c4873f -R e78d561aec5cdd8a539cc739fbc7cee6 +P 2952906c30bc2b7987f2c39837d56bd121f5817dc094e6ccdb6d4eea5e9b8d17 +R fe775ba6683934b2a7e291141b4698f9 U stephan -Z e3784a9c8c2c97335fd71d5cd2d6fc99 +Z 7d86f8939429faf240f877bf8df863ce # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8136499772..82f2910e39 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2952906c30bc2b7987f2c39837d56bd121f5817dc094e6ccdb6d4eea5e9b8d17 \ No newline at end of file +99c0941f1c006622932a9cca12661f354f363a6c8a2b5675ea66149e0a9eb927 \ No newline at end of file From a22a2d266f8dd76ad5ae41c2cacc9de6afc92b9f Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 16:42:22 +0000 Subject: [PATCH 134/148] Add SQLTester --keep-going flag to allow it to continue to the next script after an error. FossilOrigin-Name: 4d635f781b55ed9011bdf07ee6bed2d004b1c2ebba76aa110e26d8fe3152a733 --- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 7 ++++++- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 9448ab5d09..44c1c305bc 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -185,6 +185,8 @@ public class SQLTester { private int nTest = 0; //! True to enable column name output from execSql() private boolean emitColNames; + //! True to keep going regardless of how a test fails. + private boolean keepGoing = false; //! The list of available db handles. private final sqlite3[] aDb = new sqlite3[7]; //! Index into aDb of the current db. @@ -260,7 +262,8 @@ public class SQLTester { threw = true; outln("❗EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); ++nAbortedScript; - if( e.isFatal() ) throw e; + if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); + else if( e.isFatal() ) throw e; }finally{ outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",ts.getFilename()); } @@ -587,6 +590,8 @@ public class SQLTester { if( flag.equals("verbose") ){ // Use --verbose up to 3 times t.setVerbosity(t.getVerbosity() + 1); + }else if( flag.equals("keep-going") ){ + t.keepGoing = true; }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } diff --git a/manifest b/manifest index 2ead815455..859585aa31 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Document\sthat\sauto-extensions\sregistered\svia\sJNI\swill\sfail\sif\sthey\sopen\sa\sdb\s(to\savoid\striggering\san\sendless\sloop\sin\sthe\sauto-extensions).\sDiscover\sthe\shard\sway\sthat\sJDKv19\screates\sdifferent\smangled\sJNI\snames\sfor\ssome\sfunctions\sthan\sJDKv8.\sStart\sreformatting\smuch\sof\sthe\sJNI\sAPI\sdecls\sin\sprep\sfor\smaking\sthem\seven\slonger. -D 2023-08-10T13:10:25.060 +C Add\sSQLTester\s--keep-going\sflag\sto\sallow\sit\sto\scontinue\sto\sthe\snext\sscript\safter\san\serror. +D 2023-08-10T16:42:22.602 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -266,7 +266,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java f5b5ffdfeb6da35556b76f25694bdd862e67f101a88fe35cafb0b7f0af667067 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java ecb989115a421088e2772d6125cd872cd345d0c422c50aa1ce1221c61fcd1f88 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 2952906c30bc2b7987f2c39837d56bd121f5817dc094e6ccdb6d4eea5e9b8d17 -R fe775ba6683934b2a7e291141b4698f9 +P 99c0941f1c006622932a9cca12661f354f363a6c8a2b5675ea66149e0a9eb927 +R 9c64466da06e190239e7e69c9a826d4b U stephan -Z 7d86f8939429faf240f877bf8df863ce +Z f63f0a2b4c4b5ac0034a79c60a02bab8 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 82f2910e39..32af44f10e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -99c0941f1c006622932a9cca12661f354f363a6c8a2b5675ea66149e0a9eb927 \ No newline at end of file +4d635f781b55ed9011bdf07ee6bed2d004b1c2ebba76aa110e26d8fe3152a733 \ No newline at end of file From a52b8c88456df7065a32f76f2b1d3bfbd614eeb8 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 18:57:37 +0000 Subject: [PATCH 135/148] Initial pass at 'make dist' rules for the JNI bundle, but they still need a bare-bones, posix-make-compatible makefile to include in the bundle. FossilOrigin-Name: ff54e66a4d43f2f0f8b25ded970779e6760865a05346e09b39607bb035b02bd7 --- ext/jni/GNUmakefile | 55 +++++++++++++++++++++++++++++++++++++++++++-- manifest | 14 ++++++------ manifest.uuid | 2 +- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 0d978fc99a..3c67e622b4 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -17,6 +17,7 @@ package.version := 0.0.1 package.jar := sqlite3-jni-$(package.version).jar dir.top := ../.. +dir.tool := ../../tool dir.jni := $(patsubst %/,%,$(dir $(MAKEFILE))) dir.src := $(dir.jni)/src @@ -134,9 +135,10 @@ all: $(SQLite3Jni.class) # $(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.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c $(dir.top)/sqlite3.c)) +sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h $(dir.top)/sqlite3.h)) sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c)) -sqlite3.h := $(dir.top)/sqlite3.h +sqlite3.h := $(sqlite3.canonical.h) #ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null)) # SQLITE_C_IS_SEE := 0 #else @@ -263,3 +265,52 @@ clean: distclean: clean -rm -f $(DISTCLEAN_FILES) -rm -fr $(dir.bld.c) + +######################################################################## +# disttribution bundle rules... + +ifeq (,$(filter snapshot,$(MAKECMDGOALS))) +dist-name-prefix := sqlite-jni +else +dist-name-prefix := sqlite-jni-snapshot-$(shell /usr/bin/date +%Y%m%d) +endif +dist-name := $(dist-name-prefix)-TEMP + + +dist-dir.top := $(dist-name) +dist-dir.src := $(dist-dir.top)/src +dist-dir.c := $(dist-dir.src)/c +dist.top.extras := \ + README.md + +.PHONY: dist snapshot + +dist: \ + $(bin.version-info) $(sqlite3.canonical.c) \ + $(MAKEFILE) $(MAKEFILE.dist) + @echo "Making end-user deliverables..." + @rm -fr $(dist-dir.top) + @mkdir -p $(dist-dir.top) $(dist-dir.c) + @cp -p $(dist.top.extras) $(dist-dir.top)/. + @cp -p $(dir.src.c)/*.[ch] $(dist-dir.c)/. + @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.c)/. + @cp -rp $(dir.src)/org $(dist-dir.src) + @set -e; \ + vnum=$$($(bin.version-info) --download-version); \ + vdir=$(dist-name-prefix)-$$vnum; \ + arczip=$$vdir.zip; \ + echo "Making $$arczip ..."; \ + rm -fr $$arczip $$vdir; \ + mv $(dist-dir.top) $$vdir; \ + zip -qr $$arczip $$vdir -x '*.class' -x '*~'; \ + rm -fr $$vdir; \ + ls -la $$arczip; \ + set +e; \ + unzip -lv $$arczip || echo "Missing unzip app? Not fatal." + +snapshot: dist + +.PHONY: dist-clean +clean: dist-clean +dist-clean: + rm -fr $(dist-name) $(wildcard sqlite-jni-*.zip) diff --git a/manifest b/manifest index 1a36810b29..09b278867e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\strunk\sinto\sjni\sbranch\sfor\sthe\snewly-relocated\sversion-info\stool. -D 2023-08-10T17:39:26.960 +C Initial\spass\sat\s'make\sdist'\srules\sfor\sthe\sJNI\sbundle,\sbut\sthey\sstill\sneed\sa\sbare-bones,\sposix-make-compatible\smakefile\sto\sinclude\sin\sthe\sbundle. +D 2023-08-10T18:57:37.830 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ 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 b579d1c9a55e54ca66859d77218226544402896602b39432070f6a3cbdba0cc7 +F ext/jni/GNUmakefile 5a3e20bab883979f7b74fc21ba75dda5300e569cd1d61250c424fae0318c9381 F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d F ext/jni/src/c/sqlite3-jni.c fa251d2033d2210a88ac6190db923f76681be609b97f840360318ab366f3cbdd F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 @@ -2064,7 +2064,7 @@ F tool/symbols.sh 1612bd947750e21e7b47befad5f6b3825b06cce0705441f903bf35ced65ae9 F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003 F tool/vdbe-compress.tcl 1dcb7632e57cf57105248029e6e162fddaf6c0fccb3bb9e6215603752c5a2d4a F tool/vdbe_profile.tcl 3ac5a4a9449f4baf77059358ea050db3e34395ccf59c5464d29b91746d5b961e -F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 w ext/wasm/version-info.c +F tool/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 49a486c5069de041aedcbde4de178293e0463ae9918ecad7539eedf0ec77a139 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f @@ -2089,8 +2089,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4d635f781b55ed9011bdf07ee6bed2d004b1c2ebba76aa110e26d8fe3152a733 4b0871fd367b6d9706e892aa13f64604967f5e3ba92381960f73aeabd3d23f84 -R 30af3e66dbd0b2532a573f872e33a7d9 +P cc8e8cba67c0dcfb9b416041a19456cf5248d909f3efb6fee707a5950be4f374 +R 235efa3b2867de98f67e2a3f239b46e6 U stephan -Z 514337a044b3ac61c7ad0f7afbdbd01a +Z c61740ae33854adb8fa7a57e62ce6199 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b4d9b8bfeb..4bd0784d6f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cc8e8cba67c0dcfb9b416041a19456cf5248d909f3efb6fee707a5950be4f374 \ No newline at end of file +ff54e66a4d43f2f0f8b25ded970779e6760865a05346e09b39607bb035b02bd7 \ No newline at end of file From 0c08c8c208a89a6a27dc3e879ccc7c36e13f453f Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 20:52:14 +0000 Subject: [PATCH 136/148] Add a working dist zip file for the JNI bits. FossilOrigin-Name: d6a4d212ceba662470d8957b6a8d7075d18a84bd0d3e13ce7adcab03604fc3b7 --- ext/jni/GNUmakefile | 35 +++++++++++++++---------- ext/jni/README.md | 36 ++++++++++++++++---------- ext/jni/jar-dist.make | 59 +++++++++++++++++++++++++++++++++++++++++++ manifest | 15 ++++++----- manifest.uuid | 2 +- 5 files changed, 112 insertions(+), 35 deletions(-) create mode 100644 ext/jni/jar-dist.make diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 3c67e622b4..6e21adc22a 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -2,8 +2,10 @@ # build assumes a Linux-like system. default: all -JDK_HOME ?= $(HOME)/jdk/current -# /usr/lib/jvm/default-javajava-19-openjdk-amd64 +JAVA_HOME ?= $(HOME)/jdk/current +# e.g. /usr/lib/jvm/default-javajava-19-openjdk-amd64 +JDK_HOME ?= $(JAVA_HOME) +# ^^^ JDK_HOME is not as widely used as JAVA_HOME bin.javac := $(JDK_HOME)/bin/javac bin.java := $(JDK_HOME)/bin/java bin.jar := $(JDK_HOME)/bin/jar @@ -13,8 +15,7 @@ endif MAKEFILE := $(lastword $(MAKEFILE_LIST)) $(MAKEFILE): -package.version := 0.0.1 -package.jar := sqlite3-jni-$(package.version).jar +package.jar := sqlite3-jni.jar dir.top := ../.. dir.tool := ../../tool @@ -246,10 +247,17 @@ tester: endif tests: test tester +package.jar.in := $(abspath $(dir.src)/jar.in) +CLEAN_FILES += $(package.jar.in) +$(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) + cd $(dir.src); ls -1 org/sqlite/jni/*.* > $@ + @ls -la $@ + @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." + @echo "e.g. java -jar $@ -Djava.library.path=bld" -$(package.jar): $(CLASS_FILES) $(MAKEFILE) +$(package.jar): $(CLASS_FILES) $(MAKEFILE) $(package.jar.in) rm -f $(dir.src)/c/*~ $(dir.src.jni)/*~ - $(bin.jar) -cfe $@ org.sqlite.Tester1 -C src org -C src c + cd $(dir.src); $(bin.jar) -cfe ../$@ org.sqlite.jni.Tester1 @$(package.jar.in) jar: $(package.jar) @@ -279,7 +287,6 @@ dist-name := $(dist-name-prefix)-TEMP dist-dir.top := $(dist-name) dist-dir.src := $(dist-dir.top)/src -dist-dir.c := $(dist-dir.src)/c dist.top.extras := \ README.md @@ -287,22 +294,24 @@ dist.top.extras := \ dist: \ $(bin.version-info) $(sqlite3.canonical.c) \ - $(MAKEFILE) $(MAKEFILE.dist) + $(package.jar) $(MAKEFILE) @echo "Making end-user deliverables..." @rm -fr $(dist-dir.top) - @mkdir -p $(dist-dir.top) $(dist-dir.c) + @mkdir -p $(dist-dir.src) @cp -p $(dist.top.extras) $(dist-dir.top)/. - @cp -p $(dir.src.c)/*.[ch] $(dist-dir.c)/. - @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.c)/. - @cp -rp $(dir.src)/org $(dist-dir.src) + @cp -p jar-dist.make $(dist-dir.top)/Makefile + @cp -p $(dir.src.c)/*.[ch] $(dist-dir.src)/. + @cp -p $(sqlite3.canonical.c) $(sqlite3.canonical.h) $(dist-dir.src)/. @set -e; \ vnum=$$($(bin.version-info) --download-version); \ + vjar=$$($(bin.version-info) --version); \ vdir=$(dist-name-prefix)-$$vnum; \ arczip=$$vdir.zip; \ + cp -p $(package.jar) $(dist-dir.top)/sqlite3-jni-$${vjar}.jar; \ echo "Making $$arczip ..."; \ rm -fr $$arczip $$vdir; \ mv $(dist-dir.top) $$vdir; \ - zip -qr $$arczip $$vdir -x '*.class' -x '*~'; \ + zip -qr $$arczip $$vdir; \ rm -fr $$vdir; \ ls -la $$arczip; \ set +e; \ diff --git a/ext/jni/README.md b/ext/jni/README.md index 5b985a0550..80486d4a07 100644 --- a/ext/jni/README.md +++ b/ext/jni/README.md @@ -2,7 +2,16 @@ SQLite3 via JNI ======================================================================== This directory houses a Java Native Interface (JNI) binding for the -sqlite3 API. +sqlite3 API. If you are reading this from the distribution ZIP file, +links to resources in the canonical source tree will note work. The +canonical copy of this file can be browsed at: + + + +Technical support is available in the forum: + + + > **FOREWARNING:** this subproject is very much in development and subject to any number of changes. Please do not rely on any @@ -23,7 +32,8 @@ Project goals/requirements: - 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. + insofar as they neither interfere with each other nor become + a maintenance burden for the sqlite developers. Non-goals: @@ -34,11 +44,8 @@ Non-goals: 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. +- Lots of APIs left to bind. Most "day-to-day" functionality is already + in place and is believed to work well. Building @@ -53,7 +60,7 @@ The canonical builds assumes a Linux-like environment and requires: Put simply: ``` -$ export JDK_HOME=/path/to/jdk/root +$ export JAVA_HOME=/path/to/jdk/root $ make $ make test $ make clean @@ -96,10 +103,9 @@ Known consequences and limitations of this discrepancy include: - Names of databases, tables, and collations must not contain characters which differ in MUTF-8 and UTF-8, or certain APIs will - mis-translate them on their way between languages. The - sqlite3_trace_v2() implementation is also currently affected by - this, in that it will necessarily translate traced SQL statements to - MUTF-8. + mis-translate them on their way between languages. APIs which + transfer other client-side data to Java take extra care to + convert the data at the cost of performance. [modutf8]: https://docs.oracle.com/javase/8/docs/api/java/io/DataInput.html#modified-utf-8 @@ -164,10 +170,13 @@ 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."; @@ -179,8 +188,7 @@ int rc = sqlite3_create_collation(db, "mycollation", SQLITE_UTF8, new Collation( 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. + if desired. - No capabilities of the C API are lost or unduly obscured via the above API reshaping, so power users need not make any compromises. diff --git a/ext/jni/jar-dist.make b/ext/jni/jar-dist.make new file mode 100644 index 0000000000..9f9d13002a --- /dev/null +++ b/ext/jni/jar-dist.make @@ -0,0 +1,59 @@ +#!/this/is/make +#^^^^ help emacs out +# +# This is a POSIX-make-compatible makefile for building the sqlite3 +# JNI library from "dist" zip file. It must be edited to set the +# proper top-level JDK directory and, depending on the platform, add a +# platform-specific -I directory. It should build as-is with any +# 2020s-era version of gcc or clang. It requires JDK version 8 or +# higher. + +default: all + +JAVA_HOME = /usr/lib/jvm/java-1.8.0-openjdk-amd64 +CFLAGS = \ + -fPIC \ + -Isrc \ + -I$(JAVA_HOME)/include \ + -I$(JAVA_HOME)/include/linux \ + -I$(JAVA_HOME)/include/apple \ + -I$(JAVA_HOME)/include/bsd \ + -Wall + +SQLITE_OPT = \ + -DSQLITE_ENABLE_RTREE \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -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_THREADSAFE=0 \ + -DSQLITE_TEMP_STORE=2 \ + -DSQLITE_USE_URI=1 \ + -DSQLITE_ENABLE_FTS5 \ + -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.dll = libsqlite3-jni.so +$(sqlite3-jni.dll): + @echo "************************************************************************"; \ + echo "*** If this fails to build, be sure to edit this makefile ***"; \ + echo "*** to configure it for your system. ***"; \ + echo "************************************************************************" + $(CC) $(CFLAGS) $(SQLITE_OPT) \ + src/sqlite3-jni.c -shared -o $@ + @echo "Now try running it with: make test" + +test: $(sqlite3-jni.dll) + java -jar -Djava.library.path=. sqlite3-jni-*.jar + +clean: + -rm -f $(sqlite3-jni.dll) + +all: $(sqlite3-jni.dll) diff --git a/manifest b/manifest index 09b278867e..b744c224f3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\spass\sat\s'make\sdist'\srules\sfor\sthe\sJNI\sbundle,\sbut\sthey\sstill\sneed\sa\sbare-bones,\sposix-make-compatible\smakefile\sto\sinclude\sin\sthe\sbundle. -D 2023-08-10T18:57:37.830 +C Add\sa\sworking\sdist\szip\sfile\sfor\sthe\sJNI\sbits. +D 2023-08-10T20:52:14.899 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,8 +231,9 @@ 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 5a3e20bab883979f7b74fc21ba75dda5300e569cd1d61250c424fae0318c9381 -F ext/jni/README.md e965674505e105626127ad45e628e4d19fcd379cdafc4d23c814c1ac2c55681d +F ext/jni/GNUmakefile d228f18de85e7f2f80e05edce3cc4f32da3c45a308e4e921807ca88279010871 +F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb +F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 F ext/jni/src/c/sqlite3-jni.c fa251d2033d2210a88ac6190db923f76681be609b97f840360318ab366f3cbdd F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 @@ -2089,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P cc8e8cba67c0dcfb9b416041a19456cf5248d909f3efb6fee707a5950be4f374 -R 235efa3b2867de98f67e2a3f239b46e6 +P ff54e66a4d43f2f0f8b25ded970779e6760865a05346e09b39607bb035b02bd7 +R 97317c60f95c647f5cb8b6c80518e6d5 U stephan -Z c61740ae33854adb8fa7a57e62ce6199 +Z 906b52857ac9716df6a6b1f3a6f4d258 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 4bd0784d6f..507f990cea 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -ff54e66a4d43f2f0f8b25ded970779e6760865a05346e09b39607bb035b02bd7 \ No newline at end of file +d6a4d212ceba662470d8957b6a8d7075d18a84bd0d3e13ce7adcab03604fc3b7 \ No newline at end of file From 5c9f5e4022388a8e73907daa2fbad15efb858e5b Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 21:29:59 +0000 Subject: [PATCH 137/148] Work around jdk8 and jdk19 mangling the C name of sqlite3_db_config() differently. Correct the variadic arg handling of the JNI-side subset of sqlite3_db_config() options. FossilOrigin-Name: 746894c3c043c47f8b4c231de8921df81c5d0634260d299359bea73132dc7867 --- ext/jni/src/c/sqlite3-jni.c | 29 +++++++++++++++------- ext/jni/src/c/sqlite3-jni.h | 6 ++--- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 11 ++++---- ext/jni/src/org/sqlite/jni/Tester1.java | 6 ++++- manifest | 18 +++++++------- manifest.uuid | 2 +- 6 files changed, 44 insertions(+), 28 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index cac9734480..860d4a4dad 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -1367,7 +1367,7 @@ static void setupOutputPointer(JNIEnv * const env, const char *zClassName, setter = pCache->fidValue; }else{ const jclass klazz = (*env)->GetObjectClass(env, jOut); - //MARKER(("%s => %s\n", zClassName, zTypeSig)); + /*MARKER(("%s => %s\n", zClassName, zTypeSig));*/ setter = (*env)->GetFieldID(env, klazz, "value", zTypeSig); EXCEPTION_IS_FATAL("setupOutputPointer() could not find OutputPointer.*.value"); if(pCache){ @@ -2352,15 +2352,12 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( /* sqlite3_db_config() for (int,int*) */ /* ACHTUNG: openjdk v19 creates a different mangled name for this - function than openjdk v8 does. It is not yet know when that - incompatibility was introduced, so we cannot yet reliably #if it - here. */ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2)( - JENV_CSELF, jobject jDb, jint op, jobject jOut + function than openjdk v8 does. */ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut ){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); int rc; - switch( ps ? op : 0 ){ case SQLITE_DBCONFIG_ENABLE_FKEY: case SQLITE_DBCONFIG_ENABLE_TRIGGER: @@ -2381,7 +2378,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_ case SQLITE_DBCONFIG_STMT_SCANSTATUS: case SQLITE_DBCONFIG_REVERSE_SCANORDER: { int pOut = 0; - rc = sqlite3_db_config( ps->pDb, (int)op, &pOut ); + rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut ); if( 0==rc && jOut ){ OutputPointer_set_Int32(env, jOut, pOut); } @@ -2390,7 +2387,21 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_ default: rc = SQLITE_MISUSE; } - return rc; + return (jint)rc; +} + +/** + This is a workaround for openjdk v19 (and possibly others) encoding + this function's name differently than JDK v8 does. If we do not + install both names for this function then Java will not be able to + find the function in both environments. +*/ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + env, jKlazz, jDb, op, onOff, jOut + ); } JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 1054be991d..af8c0662e1 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1118,10 +1118,10 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_db_config - * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;)I + * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILorg_sqlite_jni_OutputPointer_Int32_2 - (JNIEnv *, jclass, jobject, jint, jobject); +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2 + (JNIEnv *, jclass, jobject, jint, jint, jobject); /* * Class: org_sqlite_jni_SQLite3Jni diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 945dd80ad9..451b328790 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -512,13 +512,14 @@ public final class SQLite3Jni { SQLITE_DBCONFIG_... options which uses this call form. */ public static native int sqlite3_db_config( - @NotNull sqlite3 db, int op, @Nullable OutputPointer.Int32 out + @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out ); + /** - Overload for sqlite3_db_config() calls which take (int,const - char*) variadic arguments. As of SQLite3 v3.43 the only such - option is SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op - is not SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be + Overload for sqlite3_db_config() calls which take a (const char*) + variadic argument. As of SQLite3 v3.43 the only such option is + SQLITE_DBCONFIG_MAINDBNAME. Returns SQLITE_MISUSE if op is not + SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be extended in future versions. */ public static native int sqlite3_db_config( diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 3c67076469..33f0fbbb43 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -147,8 +147,12 @@ public class Tester1 { sqlite3 db = out.getValue(); affirm(0 == rc); affirm(0 < db.getNativePointer()); + sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null) + /* This function has different mangled names in jdk8 vs jdk19, + and this call is here to ensure that the build fails + if it cannot find both names. */; sqlite3_close_v2(db); - affirm(0 == db.getNativePointer()); + affirm(0 == db.getNativePointer()); } private static void testCompileOption(){ diff --git a/manifest b/manifest index b744c224f3..c3653aa4ac 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\sworking\sdist\szip\sfile\sfor\sthe\sJNI\sbits. -D 2023-08-10T20:52:14.899 +C Work\saround\sjdk8\sand\sjdk19\smangling\sthe\sC\sname\sof\ssqlite3_db_config()\sdifferently.\sCorrect\sthe\svariadic\sarg\shandling\sof\sthe\sJNI-side\ssubset\sof\ssqlite3_db_config()\soptions. +D 2023-08-10T21:29:59.469 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,8 +234,8 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile d228f18de85e7f2f80e05edce3cc4f32da3c45a308e4e921807ca88279010871 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c fa251d2033d2210a88ac6190db923f76681be609b97f840360318ab366f3cbdd -F ext/jni/src/c/sqlite3-jni.h b19a104e0566440af566366cea72188bd994a96ba85c3f196acaa6f4a4609a55 +F ext/jni/src/c/sqlite3-jni.c 709205926615161bcd9f657a7ee29f7348da1811ed2d64fc45d7ae62aa3ab8c9 +F ext/jni/src/c/sqlite3-jni.h 471da2a2ded8425a38a01111ea04a800e9035ae87450045fc7a945be78210d7b F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -254,8 +254,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java ca91d7fdd334989ce0514a612878e329cdced5d3697d2357f938c3cf1a68e54d -F ext/jni/src/org/sqlite/jni/Tester1.java 22dca3ab0d93951382230f71e3cfb65898b80f12704a018c8ab9062df609b4fe +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 038401cac290a0641a0db33aada8941314f81c03f583a7fb18c88d24917a1757 +F ext/jni/src/org/sqlite/jni/Tester1.java c45ab1895774851dec30824157d4c390f49e17729588c02cd88172854ee97e74 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P ff54e66a4d43f2f0f8b25ded970779e6760865a05346e09b39607bb035b02bd7 -R 97317c60f95c647f5cb8b6c80518e6d5 +P d6a4d212ceba662470d8957b6a8d7075d18a84bd0d3e13ce7adcab03604fc3b7 +R f64708a8113a281fcc5d61dfd9215f40 U stephan -Z 906b52857ac9716df6a6b1f3a6f4d258 +Z 8c35114ab07dc03c5df44ce3aa4b9338 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 507f990cea..bd45f5cb0f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -d6a4d212ceba662470d8957b6a8d7075d18a84bd0d3e13ce7adcab03604fc3b7 \ No newline at end of file +746894c3c043c47f8b4c231de8921df81c5d0634260d299359bea73132dc7867 \ No newline at end of file From a0423e0fc1cee8eded300f1f615c7b110bfba0b0 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 10 Aug 2023 21:50:52 +0000 Subject: [PATCH 138/148] Mark _all_ JNI binding funcs as synchronized so that Java can lock them and protect our global-state access. The alternative is writing a mountain of C-side code to do the same thing. FossilOrigin-Name: afe190a940441de9bef8835c2dc6d278f861a772c3b7c7a2d399b2eabd4872e3 --- .../src/org/sqlite/jni/Fts5ExtensionApi.java | 38 +-- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 226 +++++++++--------- ext/jni/src/org/sqlite/jni/fts5_api.java | 2 +- manifest | 16 +- manifest.uuid | 2 +- 5 files changed, 142 insertions(+), 142 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java index 758ea717f0..ac041e3001 100644 --- a/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java +++ b/ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java @@ -35,52 +35,52 @@ public final class Fts5ExtensionApi extends NativePointerHolder, and Window) for details. */ - public static native int sqlite3_create_function( + public static synchronized native int sqlite3_create_function( @NotNull sqlite3 db, @NotNull String functionName, int nArg, int eTextRep, @NotNull SQLFunction func ); - public static native int sqlite3_data_count( + public static synchronized native int sqlite3_data_count( @NotNull sqlite3_stmt stmt ); - public static native String sqlite3_db_filename( + public static synchronized native String sqlite3_db_filename( @NotNull sqlite3 db, @NotNull String dbName ); @@ -511,7 +511,7 @@ public final class SQLite3Jni { variadic arguments. Returns SQLITE_MISUSE if op is not one of the SQLITE_DBCONFIG_... options which uses this call form. */ - public static native int sqlite3_db_config( + public static synchronized native int sqlite3_db_config( @NotNull sqlite3 db, int op, int onOff, @Nullable OutputPointer.Int32 out ); @@ -522,36 +522,36 @@ public final class SQLite3Jni { SQLITE_DBCONFIG_MAINDBNAME, but that set of options may be extended in future versions. */ - public static native int sqlite3_db_config( - @NotNull sqlite3 db, int op, @NotNull String mainDbName + public static synchronized native int sqlite3_db_config( + @NotNull sqlite3 db, int op, @NotNull String val ); - public static native int sqlite3_errcode(@NotNull sqlite3 db); + public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); - public static native int sqlite3_extended_errcode(@NotNull sqlite3 db); + public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); - public static native boolean sqlite3_extended_result_codes( + public static synchronized native boolean sqlite3_extended_result_codes( @NotNull sqlite3 db, boolean onoff ); - public static native String sqlite3_errmsg(@NotNull sqlite3 db); + public static synchronized native String sqlite3_errmsg(@NotNull sqlite3 db); - public static native String sqlite3_errstr(int resultCode); + public static synchronized native String sqlite3_errstr(int resultCode); /** Note that the offset values assume UTF-8-encoded SQL. */ - public static native int sqlite3_error_offset(@NotNull sqlite3 db); + public static synchronized native int sqlite3_error_offset(@NotNull sqlite3 db); - public static native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); + public static synchronized native int sqlite3_finalize(@NotNull sqlite3_stmt stmt); - public static native int sqlite3_initialize(); + public static synchronized native int sqlite3_initialize(); - public static native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); + public static synchronized native long sqlite3_last_insert_rowid(@NotNull sqlite3 db); - public static native String sqlite3_libversion(); + public static synchronized native String sqlite3_libversion(); - public static native int sqlite3_libversion_number(); + public static synchronized native int sqlite3_libversion_number(); /** Works like its C counterpart and makes the native pointer of the @@ -572,11 +572,11 @@ public final class SQLite3Jni { or sqlite3_open_v2() so that they have a predictible object to pass to, e.g., the sqlite3_collation_needed() callback. */ - public static native synchronized int sqlite3_open( + public static synchronized native int sqlite3_open( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb ); - public static native synchronized int sqlite3_open_v2( + public static synchronized native int sqlite3_open_v2( @Nullable String filename, @NotNull OutputPointer.sqlite3 ppDb, int flags, @Nullable String zVfs ); @@ -600,7 +600,7 @@ public final class SQLite3Jni { necessary, however, and overloads are provided which gloss over that. */ - private static native int sqlite3_prepare( + private static synchronized native int sqlite3_prepare( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset @@ -629,7 +629,7 @@ public final class SQLite3Jni { return sqlite3_prepare(db, utf8, utf8.length, outStmt, null); } - private static native int sqlite3_prepare_v2( + private static synchronized native int sqlite3_prepare_v2( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset @@ -658,7 +658,7 @@ public final class SQLite3Jni { return sqlite3_prepare_v2(db, utf8, utf8.length, outStmt, null); } - private static native int sqlite3_prepare_v3( + private static synchronized native int sqlite3_prepare_v3( @NotNull sqlite3 db, @NotNull byte[] sqlUtf8, int maxBytes, int prepFlags, @NotNull OutputPointer.sqlite3_stmt outStmt, @Nullable OutputPointer.Int32 pTailOffset @@ -687,13 +687,13 @@ public final class SQLite3Jni { return sqlite3_prepare_v3(db, utf8, utf8.length, prepFlags, outStmt, null); } - public static native void sqlite3_progress_handler( + public static synchronized native void sqlite3_progress_handler( @NotNull sqlite3 db, int n, @Nullable ProgressHandler h ); //TODO??? void *sqlite3_preupdate_hook(...) and friends - public static native int sqlite3_reset(@NotNull sqlite3_stmt stmt); + public static synchronized native int sqlite3_reset(@NotNull sqlite3_stmt stmt); /** Works like the C API except that it has no side effects if auto @@ -701,7 +701,7 @@ public final class SQLite3Jni { */ public static synchronized native void sqlite3_reset_auto_extension(); - public static native void sqlite3_result_double( + public static synchronized native void sqlite3_result_double( @NotNull sqlite3_context cx, double v ); @@ -712,7 +712,7 @@ public final class SQLite3Jni { results in the C-level sqlite3_result_error() being called with a complaint about the invalid argument. */ - private static native void sqlite3_result_error( + private static synchronized native void sqlite3_result_error( @NotNull sqlite3_context cx, @Nullable byte[] msg, int eTextRep ); @@ -755,27 +755,27 @@ public final class SQLite3Jni { sqlite3_result_error16(cx, e.getMessage()); } - public static native void sqlite3_result_error_toobig( + public static synchronized native void sqlite3_result_error_toobig( @NotNull sqlite3_context cx ); - public static native void sqlite3_result_error_nomem( + public static synchronized native void sqlite3_result_error_nomem( @NotNull sqlite3_context cx ); - public static native void sqlite3_result_error_code( + public static synchronized native void sqlite3_result_error_code( @NotNull sqlite3_context cx, int c ); - public static native void sqlite3_result_null( + public static synchronized native void sqlite3_result_null( @NotNull sqlite3_context cx ); - public static native void sqlite3_result_int( + public static synchronized native void sqlite3_result_int( @NotNull sqlite3_context cx, int v ); - public static native void sqlite3_result_int64( + public static synchronized native void sqlite3_result_int64( @NotNull sqlite3_context cx, long v ); @@ -795,7 +795,7 @@ public final class SQLite3Jni { Note that there is no sqlite3_bind_java_object() counterpart. */ - public static native void sqlite3_result_java_object( + public static synchronized native void sqlite3_result_java_object( @NotNull sqlite3_context cx, @NotNull Object o ); @@ -853,19 +853,19 @@ public final class SQLite3Jni { sqlite3_result_text(cx, v); } - public static native void sqlite3_result_value( + public static synchronized native void sqlite3_result_value( @NotNull sqlite3_context cx, @NotNull sqlite3_value v ); - public static native void sqlite3_result_zeroblob( + public static synchronized native void sqlite3_result_zeroblob( @NotNull sqlite3_context cx, int n ); - public static native int sqlite3_result_zeroblob64( + public static synchronized native int sqlite3_result_zeroblob64( @NotNull sqlite3_context cx, long n ); - private static native void sqlite3_result_blob( + private static synchronized native void sqlite3_result_blob( @NotNull sqlite3_context cx, @Nullable byte[] blob, int maxLen ); @@ -885,7 +885,7 @@ public final class SQLite3Jni { If maxLen is larger than blob.length, it is truncated to that value. If it is negative, results are undefined. */ - private static native void sqlite3_result_blob64( + private static synchronized native void sqlite3_result_blob64( @NotNull sqlite3_context cx, @Nullable byte[] blob, long maxLen ); @@ -895,7 +895,7 @@ public final class SQLite3Jni { sqlite3_result_blob64(cx, blob, (long)(null==blob ? 0 : blob.length)); } - private static native void sqlite3_result_text( + private static synchronized native void sqlite3_result_text( @NotNull sqlite3_context cx, @Nullable byte[] text, int maxLen ); @@ -929,7 +929,7 @@ public final class SQLite3Jni { text.length, it is silently truncated to text.length. If it is negative, results are undefined. */ - private static native void sqlite3_result_text64( + private static synchronized native void sqlite3_result_text64( @NotNull sqlite3_context cx, @Nullable byte[] text, long maxLength, int encoding ); @@ -985,30 +985,30 @@ public final class SQLite3Jni { sqlite3_result_text64(cx, b, b.length, SQLITE_UTF16BE); } - public static native RollbackHook sqlite3_rollback_hook( + public static synchronized native RollbackHook sqlite3_rollback_hook( @NotNull sqlite3 db, @Nullable RollbackHook hook ); //! Sets or unsets (if auth is null) the current authorizer. - public static native int sqlite3_set_authorizer( + public static synchronized native int sqlite3_set_authorizer( @NotNull sqlite3 db, @Nullable Authorizer auth ); - public static native void sqlite3_set_last_insert_rowid( + public static synchronized native void sqlite3_set_last_insert_rowid( @NotNull sqlite3 db, long rowid ); - public static native int sqlite3_sleep(int ms); + public static synchronized native int sqlite3_sleep(int ms); - public static native String sqlite3_sourceid(); + public static synchronized native String sqlite3_sourceid(); - public static native int sqlite3_step(@NotNull sqlite3_stmt stmt); + public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); /** Internal impl of the public sqlite3_strglob() method. Neither argument may be NULL and both _MUST_ be NUL-terminated. */ - private static native int sqlite3_strglob( + private static synchronized native int sqlite3_strglob( @NotNull byte[] glob, @NotNull byte[] txt ); @@ -1025,7 +1025,7 @@ public final class SQLite3Jni { Internal impl of the public sqlite3_strlike() method. Neither argument may be NULL and both _MUST_ be NUL-terminated. */ - private static native int sqlite3_strlike( + private static synchronized native int sqlite3_strlike( @NotNull byte[] glob, @NotNull byte[] txt, int escChar ); @@ -1039,11 +1039,11 @@ public final class SQLite3Jni { ); } - public static native int sqlite3_threadsafe(); + public static synchronized native int sqlite3_threadsafe(); - public static native int sqlite3_total_changes(@NotNull sqlite3 db); + public static synchronized native int sqlite3_total_changes(@NotNull sqlite3 db); - public static native long sqlite3_total_changes64(@NotNull sqlite3 db); + public static synchronized native long sqlite3_total_changes64(@NotNull sqlite3 db); /** Works like C's sqlite3_trace_v2() except that the 3rd argument to that @@ -1055,33 +1055,33 @@ public final class SQLite3Jni { mapping state fails and SQLITE_ERROR if the given callback object cannot be processed propertly (i.e. an internal error). */ - public static native int sqlite3_trace_v2( + public static synchronized native int sqlite3_trace_v2( @NotNull sqlite3 db, int traceMask, @Nullable Tracer tracer ); - public static native UpdateHook sqlite3_update_hook( + public static synchronized native UpdateHook sqlite3_update_hook( sqlite3 db, UpdateHook hook ); - public static native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); + public static synchronized native byte[] sqlite3_value_blob(@NotNull sqlite3_value v); - public static native int sqlite3_value_bytes(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_bytes(@NotNull sqlite3_value v); - public static native int sqlite3_value_bytes16(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_bytes16(@NotNull sqlite3_value v); - public static native double sqlite3_value_double(@NotNull sqlite3_value v); + public static synchronized native double sqlite3_value_double(@NotNull sqlite3_value v); - public static native sqlite3_value sqlite3_value_dupe( + public static synchronized native sqlite3_value sqlite3_value_dupe( @NotNull sqlite3_value v ); - public static native int sqlite3_value_encoding(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_encoding(@NotNull sqlite3_value v); - public static native void sqlite3_value_free(@Nullable sqlite3_value v); + public static synchronized native void sqlite3_value_free(@Nullable sqlite3_value v); - public static native int sqlite3_value_int(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_int(@NotNull sqlite3_value v); - public static native long sqlite3_value_int64(@NotNull sqlite3_value v); + public static synchronized native long sqlite3_value_int64(@NotNull sqlite3_value v); /** If the given value was set using sqlite3_result_java_value() then @@ -1090,7 +1090,7 @@ public final class SQLite3Jni { It is up to the caller to inspect the object to determine its type, and cast it if necessary. */ - public static native Object sqlite3_value_java_object( + public static synchronized native Object sqlite3_value_java_object( @NotNull sqlite3_value v ); @@ -1111,41 +1111,41 @@ public final class SQLite3Jni { See sqlite3_value_text_utf8() for how to extract text in standard UTF-8. */ - public static native String sqlite3_value_text(@NotNull sqlite3_value v); + public static synchronized native String sqlite3_value_text(@NotNull sqlite3_value v); /** The sqlite3_value counterpart of sqlite3_column_text_utf8(). */ - public static native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); + public static synchronized native byte[] sqlite3_value_text_utf8(@NotNull sqlite3_value v); - public static native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); + public static synchronized native byte[] sqlite3_value_text16(@NotNull sqlite3_value v); - public static native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); + public static synchronized native byte[] sqlite3_value_text16le(@NotNull sqlite3_value v); - public static native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); + public static synchronized native byte[] sqlite3_value_text16be(@NotNull sqlite3_value v); - public static native int sqlite3_value_type(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_type(@NotNull sqlite3_value v); - public static native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_numeric_type(@NotNull sqlite3_value v); - public static native int sqlite3_value_nochange(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_nochange(@NotNull sqlite3_value v); - public static native int sqlite3_value_frombind(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_frombind(@NotNull sqlite3_value v); - public static native int sqlite3_value_subtype(@NotNull sqlite3_value v); + public static synchronized native int sqlite3_value_subtype(@NotNull sqlite3_value v); /** Cleans up all per-JNIEnv and per-db state managed by the library then calls the C-native sqlite3_shutdown(). */ - public static native int sqlite3_shutdown(); + public static synchronized native int sqlite3_shutdown(); /** This is NOT part of the public API. It exists solely as a place to hook in arbitrary C-side code during development and testing of this library. */ - static native void sqlite3_do_something_for_developer(); + static synchronized native void sqlite3_do_something_for_developer(); ////////////////////////////////////////////////////////////////////// // SQLITE_... constants follow... diff --git a/ext/jni/src/org/sqlite/jni/fts5_api.java b/ext/jni/src/org/sqlite/jni/fts5_api.java index 4df8a748e2..43b3d62ded 100644 --- a/ext/jni/src/org/sqlite/jni/fts5_api.java +++ b/ext/jni/src/org/sqlite/jni/fts5_api.java @@ -57,7 +57,7 @@ public final class fts5_api extends NativePointerHolder { // void (*xDestroy)(void*) // ); - public native int xCreateFunction(@NotNull String name, + public synchronized native int xCreateFunction(@NotNull String name, @Nullable Object userData, @NotNull fts5_extension_function xFunction); diff --git a/manifest b/manifest index c3653aa4ac..4016d95687 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Work\saround\sjdk8\sand\sjdk19\smangling\sthe\sC\sname\sof\ssqlite3_db_config()\sdifferently.\sCorrect\sthe\svariadic\sarg\shandling\sof\sthe\sJNI-side\ssubset\sof\ssqlite3_db_config()\soptions. -D 2023-08-10T21:29:59.469 +C Mark\s_all_\sJNI\sbinding\sfuncs\sas\ssynchronized\sso\sthat\sJava\scan\slock\sthem\sand\sprotect\sour\sglobal-state\saccess.\sThe\salternative\sis\swriting\sa\smountain\sof\sC-side\scode\sto\sdo\sthe\ssame\sthing. +D 2023-08-10T21:50:52.042 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -244,7 +244,7 @@ F ext/jni/src/org/sqlite/jni/CollationNeeded.java ad67843b6dd1c06b6b0a1dc72887b7 F ext/jni/src/org/sqlite/jni/CommitHook.java 87c6a8e5138c61a8eeff018fe16d23f29219150239746032687f245938baca1a F ext/jni/src/org/sqlite/jni/Fts5.java 13844685231e8b4840a706db3bed84d5dfcf15be0ae7e809eac40420dba24901 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 -F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java c908e5fdf6f5d15e388144fcd8160a3f46c18dade749f1b747122d2d37f2e726 +F ext/jni/src/org/sqlite/jni/Fts5ExtensionApi.java 01f890105c6b7edbbad1c0f5635f783cea62c4b2ae694a71e76514a936ee03ec F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7babcd11a0c308a832b7940574259bcc F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 @@ -254,13 +254,13 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 038401cac290a0641a0db33aada8941314f81c03f583a7fb18c88d24917a1757 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 836822ecf2f346d2218609fa68ddb3b595361663890493271635726e776cb57b F ext/jni/src/org/sqlite/jni/Tester1.java c45ab1895774851dec30824157d4c390f49e17729588c02cd88172854ee97e74 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee -F ext/jni/src/org/sqlite/jni/fts5_api.java 8c6b32455d7f85ee3f7f3e71c148bb3c2106f1d5484017daddfd560dd69d4f66 +F ext/jni/src/org/sqlite/jni/fts5_api.java 5198be71c162e3e0cb1f4962a7cdf0d7596e8af53f70c4af6db24aab8d53d9ba F ext/jni/src/org/sqlite/jni/fts5_extension_function.java ac825035d7d83fc7fd960347abfa6803e1614334a21533302041823ad5fc894c F ext/jni/src/org/sqlite/jni/fts5_tokenizer.java e530b36e6437fcc500e95d5d75fbffe272bdea20d2fac6be2e1336c578fba98b F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c388ec3bc1de547f098a0217158fc @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P d6a4d212ceba662470d8957b6a8d7075d18a84bd0d3e13ce7adcab03604fc3b7 -R f64708a8113a281fcc5d61dfd9215f40 +P 746894c3c043c47f8b4c231de8921df81c5d0634260d299359bea73132dc7867 +R e1ccd8a6a6627664a18e71218906f2ad U stephan -Z 8c35114ab07dc03c5df44ce3aa4b9338 +Z 898733fffb725ecf1391b4a8e291674e # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bd45f5cb0f..8bcfadfae9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -746894c3c043c47f8b4c231de8921df81c5d0634260d299359bea73132dc7867 \ No newline at end of file +afe190a940441de9bef8835c2dc6d278f861a772c3b7c7a2d399b2eabd4872e3 \ No newline at end of file From 7009c43eaf7181a9b783e3dacf7d93bb28263893 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 11 Aug 2023 18:04:53 +0000 Subject: [PATCH 139/148] Bind sqlite3_sql() and sqlite3_expanded_sql() to JNI. Start marking C-side functions which would need explicit mutex support if we remove 'synchronized' from their Java entry points (but there are many more left to mark). FossilOrigin-Name: c7fb32d1ef30d34449c3289c384ce33317c770927534af20d4b96fa385da40bc --- ext/jni/src/c/sqlite3-jni.c | 80 ++++++++++++++++--- ext/jni/src/c/sqlite3-jni.h | 16 ++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 22 ++++- ext/jni/src/org/sqlite/jni/Tester1.java | 12 +++ .../src/org/sqlite/jni/tester/SQLTester.java | 8 +- manifest | 20 ++--- manifest.uuid | 2 +- 7 files changed, 134 insertions(+), 26 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 860d4a4dad..f8461abfa1 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -311,7 +311,7 @@ static const struct { #define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) /* Marker for code which needs(?) to be made thread-safe. */ -#define FIXME_THREADING +#define FIXME_THREADING(REASON) enum { /** @@ -557,7 +557,7 @@ static void * s3jni_malloc(JNIEnv * const env, size_t n){ insofar as possible. Calls (*env)->FatalError() if allocation of an entry fails. That's hypothetically possible but "shouldn't happen." */ -FIXME_THREADING +FIXME_THREADING(S3JniEnvCache) static S3JniEnvCache * S3JniGlobal_env_cache(JNIEnv * const env){ struct S3JniEnvCache * row = S3JniGlobal.envCache.aHead; for( ; row; row = row->pNext ){ @@ -763,6 +763,7 @@ static jstring s3jni_text16_to_jstring(JNIEnv * const env, const void * const p, System.out.println(e.getMessage()); // Hi } */ +FIXME_THREADING(S3JniEnvCache) static char * s3jni_exception_error_msg(JNIEnv * const env, jthrowable jx ){ S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); jmethodID mid; @@ -862,7 +863,7 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest /** Clears s's state and moves it to the free-list. */ -FIXME_THREADING +FIXME_THREADING(perDb) static void S3JniDb_set_aside(S3JniDb * const s){ if(s){ JNIEnv * const env = s->env; @@ -930,7 +931,7 @@ static void S3JniEnvCache_clear(S3JniEnvCache * const p){ Results are undefined if a Java-side db uses the API from the given JNIEnv after this call. */ -FIXME_THREADING +FIXME_THREADING(perDb) static void S3JniDb_free_for_env(JNIEnv *env){ S3JniDb * ps = S3JniGlobal.perDb.aUsed; S3JniDb * pNext = 0; @@ -999,7 +1000,7 @@ static void S3JniGlobal_S3JniEnvCache_clear(void){ This simple cache catches >99% of searches in the current (2023-07-31) tests. */ -FIXME_THREADING +FIXME_THREADING(S3JniEnvCache) static S3JniNphCache * S3JniGlobal_nph_cache(JNIEnv * const env, const char *zClassName){ /** According to: @@ -1196,7 +1197,8 @@ static void S3JniDb_dump(S3JniDb *s){ required for functions, like sqlite3_context_db_handle(), which return a (sqlite3*) but do not take one as an argument. */ -FIXME_THREADING +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb, int allocIfNeeded){ S3JniDb * s = S3JniGlobal.perDb.aUsed; @@ -1227,7 +1229,7 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, called from the context of a separate JNIEnv than the one mapped to in the returned object. Returns 0 if no match is found. */ -FIXME_THREADING +FIXME_THREADING(perDb) static S3JniDb * S3JniDb_for_db2(sqlite3 *pDb){ S3JniDb * s = S3JniGlobal.perDb.aUsed; for( ; pDb && s; s = s->pNext){ @@ -1877,7 +1879,7 @@ WRAP_INT_SVALUE(1value_1type, sqlite3_value_type) #if S3JNI_ENABLE_AUTOEXT /* Central auto-extension handler. */ -FIXME_THREADING +FIXME_THREADING(autoExt) static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, const struct sqlite3_api_routines *ignored){ S3JniAutoExtension const * pAX = S3JniGlobal.autoExt.pHead; @@ -1929,6 +1931,7 @@ static int s3jni_auto_extension(sqlite3 *pDb, const char **pzErr, return rc; } +FIXME_THREADING(autoExt) JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ static int once = 0; S3JniAutoExtension * ax; @@ -1947,6 +1950,7 @@ JDECL(jint,1auto_1extension)(JENV_OSELF, jobject jAutoExt){ } #endif /* S3JNI_ENABLE_AUTOEXT */ +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ int rc; @@ -1961,26 +1965,31 @@ JDECL(jint,1bind_1blob)(JENV_CSELF, jobject jpStmt, return (jint)rc; } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1double)(JENV_CSELF, jobject jpStmt, jint ndx, jdouble val){ return (jint)sqlite3_bind_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (double)val); } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1int)(JENV_CSELF, jobject jpStmt, jint ndx, jint val){ return (jint)sqlite3_bind_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)val); } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1int64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong val){ return (jint)sqlite3_bind_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_int64)val); } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1null)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_bind_null(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName){ int rc = 0; jbyte * const pBuf = JBA_TOC(jName); @@ -1992,6 +2001,7 @@ JDECL(jint,1bind_1parameter_1index)(JENV_CSELF, jobject jpStmt, jbyteArray jName return rc; } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, jint ndx, jbyteArray baData, jint nMax){ if(baData){ @@ -2005,11 +2015,13 @@ JDECL(jint,1bind_1text)(JENV_CSELF, jobject jpStmt, } } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1zeroblob)(JENV_CSELF, jobject jpStmt, jint ndx, jint n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (int)n); } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1bind_1zeroblob64)(JENV_CSELF, jobject jpStmt, jint ndx, jlong n){ return (jint)sqlite3_bind_zeroblob(PtrGet_sqlite3_stmt(jpStmt), (int)ndx, (sqlite3_uint64)n); @@ -2031,6 +2043,7 @@ static int s3jni_busy_handler(void* pState, int n){ return rc; } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); int rc = 0; @@ -2060,6 +2073,8 @@ JDECL(jint,1busy_1handler)(JENV_CSELF, jobject jDb, jobject jBusy){ : sqlite3_busy_handler(ps->pDb, 0, 0); } +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); if( ps ){ @@ -2070,7 +2085,8 @@ JDECL(jint,1busy_1timeout)(JENV_CSELF, jobject jDb, jint ms){ } #if S3JNI_ENABLE_AUTOEXT -JDECL(jboolean,1cancel_1auto_1extension)(JENV_OSELF, jobject jAutoExt){ +FIXME_THREADING(autoExt) +JDECL(jboolean,1cancel_1auto_1extension)(JENV_CSELF, jobject jAutoExt){ S3JniAutoExtension * ax;; if( S3JniGlobal.autoExt.isRunning ) return JNI_FALSE; for( ax = S3JniGlobal.autoExt.pHead; ax; ax = ax->pNext ){ @@ -2102,10 +2118,14 @@ static jint s3jni_close_db(JNIEnv * const env, jobject jDb, int version){ return (jint)rc; } +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) JDECL(jint,1close_1v2)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 2); } +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) JDECL(jint,1close)(JENV_CSELF, jobject pDb){ return s3jni_close_db(env, pDb, 1); } @@ -2144,6 +2164,8 @@ static void s3jni_collation_needed_impl16(void *pState, sqlite3 *pDb, UNREF_L(jName); } +FIXME_THREADING(S3JniEnvCache) +FIXME_THREADING(perDb) JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); jclass klazz; @@ -2180,6 +2202,7 @@ JDECL(jint,1collation_1needed)(JENV_CSELF, jobject jDb, jobject jHook){ return rc; } +FIXME_THREADING(S3JniEnvCache) JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2193,21 +2216,25 @@ JDECL(jbyteArray,1column_1blob)(JENV_CSELF, jobject jpStmt, } } +FIXME_THREADING(S3JniEnvCache) JDECL(jdouble,1column_1double)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jdouble)sqlite3_column_double(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } +FIXME_THREADING(S3JniEnvCache) JDECL(jint,1column_1int)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jint)sqlite3_column_int(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } +FIXME_THREADING(S3JniEnvCache) JDECL(jlong,1column_1int64)(JENV_CSELF, jobject jpStmt, jint ndx){ return (jlong)sqlite3_column_int64(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); } +FIXME_THREADING(S3JniEnvCache) JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2216,6 +2243,7 @@ JDECL(jbyteArray,1column_1text)(JENV_CSELF, jobject jpStmt, return s3jni_new_jbyteArray(env, p, n); } +FIXME_THREADING(S3JniEnvCache) JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_stmt * const stmt = PtrGet_sqlite3_stmt(jpStmt); @@ -2224,6 +2252,7 @@ JDECL(jstring,1column_1text16)(JENV_CSELF, jobject jpStmt, return s3jni_text16_to_jstring(env, p, n); } +FIXME_THREADING(S3JniEnvCache) JDECL(jobject,1column_1value)(JENV_CSELF, jobject jpStmt, jint ndx){ sqlite3_value * const sv = sqlite3_column_value(PtrGet_sqlite3_stmt(jpStmt), (int)ndx); @@ -2253,6 +2282,7 @@ static void s3jni_rollback_hook_impl(void *pP){ (void)s3jni_commit_rollback_hook_impl(0, pP); } +FIXME_THREADING(perDb) static jobject s3jni_commit_rollback_hook(int isCommit, JNIEnv * const env,jobject jDb, jobject jHook){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); @@ -2350,6 +2380,7 @@ JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( return rc; } +FIXME_THREADING(perDb) /* sqlite3_db_config() for (int,int*) */ /* ACHTUNG: openjdk v19 creates a different mangled name for this function than openjdk v8 does. */ @@ -2404,6 +2435,7 @@ JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer ); } +FIXME_THREADING(perDb) JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); S3JniDb * const ps = pDb ? S3JniDb_for_db(env, 0, pDb, 0) : 0; @@ -2540,6 +2572,21 @@ JDECL(jstring,1errstr)(JENV_CSELF, jint rcCode){ MUTF-8 incompatibility */; } +JDECL(jstring,1expanded_1sql)(JENV_CSELF, jobject jpStmt){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + jstring rv = 0; + if( pStmt ){ + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + char * zSql = sqlite3_expanded_sql(pStmt); + OOM_CHECK(zSql); + if( zSql ){ + rv = s3jni_utf8_to_jstring(jc, zSql, -1); + sqlite3_free(zSql); + } + } + return rv; +} + JDECL(jboolean,1extended_1result_1codes)(JENV_CSELF, jobject jpDb, jboolean onoff){ int const rc = sqlite3_extended_result_codes(PtrGet_sqlite3(jpDb), onoff ? 1 : 0); @@ -3099,6 +3146,19 @@ JDECL(jint,1shutdown)(JENV_CSELF){ return sqlite3_shutdown(); } +JDECL(jstring,1sql)(JENV_CSELF, jobject jpStmt){ + sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jpStmt); + jstring rv = 0; + if( pStmt ){ + const char * zSql = 0; + S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); + zSql = sqlite3_sql(pStmt); + rv = s3jni_utf8_to_jstring(jc, zSql, -1); + OOM_CHECK(rv); + } + return rv; +} + JDECL(jint,1step)(JENV_CSELF,jobject jStmt){ int rc = SQLITE_MISUSE; sqlite3_stmt * const pStmt = PtrGet_sqlite3_stmt(jStmt); @@ -3384,7 +3444,7 @@ JDECL(void,1do_1something_1for_1developer)(JENV_CSELF){ printf("\tJNIEnv cache %u misses, %u hits\n", S3JniGlobal.metrics.envCacheMisses, S3JniGlobal.metrics.envCacheHits); - puts("UDF calls:"); + puts("Java-side UDF calls:"); #define UDF(T) printf("\t%-8s = %u\n", "x" #T, S3JniGlobal.metrics.udf.n##T) UDF(Func); UDF(Step); UDF(Final); UDF(Value); UDF(Inverse); #undef UDF diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index af8c0662e1..d8e75cd7ba 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1139,6 +1139,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1errcode (JNIEnv *, jclass, jobject); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_expanded_sql + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1expanded_1sql + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_extended_errcode @@ -1451,6 +1459,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sleep JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sourceid (JNIEnv *, jclass); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_sql + * Signature: (Lorg/sqlite/jni/sqlite3_stmt;)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1sql + (JNIEnv *, jclass, jobject); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_step diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index b6f07927cc..46488a40a9 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -472,11 +472,11 @@ public final class SQLite3Jni { @NotNull sqlite3 db, @Nullable CommitHook hook ); - public static synchronized native String sqlite3_compileoption_get( + public static native String sqlite3_compileoption_get( int n ); - public static synchronized native boolean sqlite3_compileoption_used( + public static native boolean sqlite3_compileoption_used( @NotNull String optName ); @@ -528,6 +528,8 @@ public final class SQLite3Jni { public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); + public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); + public static synchronized native int sqlite3_extended_errcode(@NotNull sqlite3 db); public static synchronized native boolean sqlite3_extended_result_codes( @@ -934,6 +936,16 @@ public final class SQLite3Jni { long maxLength, int encoding ); + + // public static synchronized native int sqlite3_status( + // int op, OutputPointer.Int32 pCurrent, OutputPointer.Int32 pHighwater, + // boolean reset + // ); + // public static synchronized native int sqlite3_status64( + // int op, OutputPointer.Int64 pCurrent, OutputPointer.Int64 pHighwater, + // boolean reset + // ); + /** Sets the current UDF result to the given bytes, which are assumed be encoded in UTF-16 using the platform's byte order. @@ -1002,6 +1014,8 @@ public final class SQLite3Jni { public static synchronized native String sqlite3_sourceid(); + public static synchronized native String sqlite3_sql(@NotNull sqlite3_stmt stmt); + public static synchronized native int sqlite3_step(@NotNull sqlite3_stmt stmt); /** @@ -1144,8 +1158,8 @@ public final class SQLite3Jni { This is NOT part of the public API. It exists solely as a place to hook in arbitrary C-side code during development and testing of this library. - */ - static synchronized native void sqlite3_do_something_for_developer(); + */ + public static synchronized native void sqlite3_do_something_for_developer(); ////////////////////////////////////////////////////////////////////// // SQLITE_... constants follow... diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 33f0fbbb43..0ae424d53a 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -390,6 +390,17 @@ public class Tester1 { sqlite3_close_v2(db); } + private static void testSql(){ + sqlite3 db = createNewDb(); + sqlite3_stmt stmt = prepare(db, "SELECT 1"); + affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); + sqlite3_finalize(stmt); + stmt = prepare(db, "SELECT ?"); + sqlite3_bind_text(stmt, 1, "hello"); + affirm( "SELECT 'hello'".equals(sqlite3_expanded_sql(stmt)) ); + sqlite3_finalize(stmt); + } + private static void testCollation(){ final sqlite3 db = createNewDb(); execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')"); @@ -1069,6 +1080,7 @@ public class Tester1 { testBindFetchDouble(); testBindFetchText(); testBindFetchBlob(); + testSql(); testCollation(); testToUtf8(); testUdf1(); diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 44c1c305bc..15ac6c6a25 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -260,7 +260,7 @@ public class SQLTester { ts.run(this); }catch(SQLTesterException e){ threw = true; - outln("❗EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); + outln("🔥EXCEPTION: ",e.getClass().getSimpleName(),": ",e.getMessage()); ++nAbortedScript; if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); else if( e.isFatal() ) throw e; @@ -583,6 +583,7 @@ public class SQLTester { public static void main(String[] argv) throws Exception{ installCustomExtensions(); + boolean dumpInternals = false; final SQLTester t = new SQLTester(); for(String a : argv){ if(a.startsWith("-")){ @@ -592,6 +593,8 @@ public class SQLTester { t.setVerbosity(t.getVerbosity() + 1); }else if( flag.equals("keep-going") ){ t.keepGoing = true; + }else if( flag.equals("internals") ){ + dumpInternals = true; }else{ throw new IllegalArgumentException("Unhandled flag: "+flag); } @@ -618,6 +621,9 @@ public class SQLTester { if( t.nAbortedScript > 0 ){ t.outln("Aborted ",t.nAbortedScript," script(s)."); } + if( dumpInternals ){ + sqlite3_do_something_for_developer(); + } } } diff --git a/manifest b/manifest index 4016d95687..8eaa3c8533 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Mark\s_all_\sJNI\sbinding\sfuncs\sas\ssynchronized\sso\sthat\sJava\scan\slock\sthem\sand\sprotect\sour\sglobal-state\saccess.\sThe\salternative\sis\swriting\sa\smountain\sof\sC-side\scode\sto\sdo\sthe\ssame\sthing. -D 2023-08-10T21:50:52.042 +C Bind\ssqlite3_sql()\sand\ssqlite3_expanded_sql()\sto\sJNI.\sStart\smarking\sC-side\sfunctions\swhich\swould\sneed\sexplicit\smutex\ssupport\sif\swe\sremove\s'synchronized'\sfrom\stheir\sJava\sentry\spoints\s(but\sthere\sare\smany\smore\sleft\sto\smark). +D 2023-08-11T18:04:53.620 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,8 +234,8 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile d228f18de85e7f2f80e05edce3cc4f32da3c45a308e4e921807ca88279010871 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 709205926615161bcd9f657a7ee29f7348da1811ed2d64fc45d7ae62aa3ab8c9 -F ext/jni/src/c/sqlite3-jni.h 471da2a2ded8425a38a01111ea04a800e9035ae87450045fc7a945be78210d7b +F ext/jni/src/c/sqlite3-jni.c 478c168346b6ee0b600433c8d1b1c59b357fdd7418a1dd18498ce9e73742d685 +F ext/jni/src/c/sqlite3-jni.h 8f9e03016fea584627219eb4a14c6b1a6ba9fffbd7bc0df9e14220a6af8cfe18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -254,8 +254,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 836822ecf2f346d2218609fa68ddb3b595361663890493271635726e776cb57b -F ext/jni/src/org/sqlite/jni/Tester1.java c45ab1895774851dec30824157d4c390f49e17729588c02cd88172854ee97e74 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 23dec2c267ef1882bb0bfc12a2c771e13e091885942344abbde90861f7b795fa +F ext/jni/src/org/sqlite/jni/Tester1.java 7716949ae434308e698da505549e751478a03f40f2775d6b5486b754ded37a79 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java ecb989115a421088e2772d6125cd872cd345d0c422c50aa1ce1221c61fcd1f88 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 5aa16634120dea614ebe3c127c69da87ba207cb658b09f600a8e8f9d2dc91e15 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 746894c3c043c47f8b4c231de8921df81c5d0634260d299359bea73132dc7867 -R e1ccd8a6a6627664a18e71218906f2ad +P afe190a940441de9bef8835c2dc6d278f861a772c3b7c7a2d399b2eabd4872e3 +R 2143ed5f6ccc929f2efe052657302ea3 U stephan -Z 898733fffb725ecf1391b4a8e291674e +Z 2327e5c9841d1baf8b0f3e308e996b38 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8bcfadfae9..3b128b9a84 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -afe190a940441de9bef8835c2dc6d278f861a772c3b7c7a2d399b2eabd4872e3 \ No newline at end of file +c7fb32d1ef30d34449c3289c384ce33317c770927534af20d4b96fa385da40bc \ No newline at end of file From 16c8c967456cc5442b94c427c573efe33df9ca7f Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 11 Aug 2023 18:59:57 +0000 Subject: [PATCH 140/148] Minor SQLTester test tweaks and have 'make tester' include the out-of-tree tests if they are found. FossilOrigin-Name: 6c8538d83495ce65dbd7417263b3b06dbbb2a649e9a61a743911944599d75ffc --- ext/jni/GNUmakefile | 25 +++++++++++++++++++++++-- ext/jni/src/c/sqlite3-jni.c | 4 ++++ ext/jni/src/org/sqlite/jni/Tester1.java | 4 ++-- manifest | 16 ++++++++-------- manifest.uuid | 2 +- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 6e21adc22a..02722d2955 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -235,17 +235,38 @@ test: $(SQLite3Jni.class) $(sqlite3-jni.dll) tester.scripts := $(sort $(wildcard $(dir.src)/tests/*.test)) tester.flags ?= # --verbose -.PHONY: tester +.PHONY: tester tester-local tester-ext ifeq (1,$(enable.tester)) -tester: $(CLASS_FILES.tester) $(sqlite3-jni.dll) +tester-local: $(CLASS_FILES.tester) $(sqlite3-jni.dll) $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ $(java.flags) -cp $(classpath) \ org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.scripts) +tester: tester-local else tester: @echo "SQLTester support is disabled. Build with enable.tester=1 to enable it." endif +tester.extdir.default := src/tests/ext +tester.extdir ?= $(tester.extdir.default) +tester.extern-scripts := $(wildcard $(tester.extdir)/*.test) +ifneq (,$(tester.extern-scripts)) +tester-ext: + $(bin.java) -ea -Djava.library.path=$(dir.bld.c) \ + $(java.flags) -cp $(classpath) \ + org.sqlite.jni.tester.SQLTester $(tester.flags) $(tester.extern-scripts) +else +tester-ext: + @echo "******************************************************"; \ + echo "*** Include the out-of-tree test suite in the 'tester'"; \ + echo "*** target by either symlinking its directory to"; \ + echo "*** $(tester.extdir.default) or passing it to make"; \ + echo "*** as tester.extdir=/path/to/that/dir."; \ + echo "******************************************************"; +endif + +tester-ext: tester-local +tester: tester-ext tests: test tester package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index f8461abfa1..fef6f0233c 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -4341,11 +4341,14 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ }; jfieldID fieldId; const ConfigFlagEntry * pConfFlag; + memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); if( (*env)->GetJavaVM(env, &S3JniGlobal.jvm) ){ (*env)->FatalError(env, "GetJavaVM() failure shouldn't be possible."); return; } +#if 0 + /* Just for sanity checking... */ (void)S3JniGlobal_env_cache(env); if( !S3JniGlobal.envCache.aHead ){ (*env)->FatalError(env, "Could not allocate JNIEnv-specific cache."); @@ -4354,6 +4357,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JENV_CSELF){ assert( 1 == S3JniGlobal.metrics.envCacheMisses ); assert( env == S3JniGlobal.envCache.aHead->env ); assert( 0 != S3JniGlobal.envCache.aHead->g.cObj ); +#endif for( pConfFlag = &aLimits[0]; pConfFlag->zName; ++pConfFlag ){ char const * zSig = (JTYPE_BOOL == pConfFlag->jtype) ? "Z" : "I"; diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 0ae424d53a..be1fa3edc2 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -396,8 +396,8 @@ public class Tester1 { affirm( "SELECT 1".equals(sqlite3_sql(stmt)) ); sqlite3_finalize(stmt); stmt = prepare(db, "SELECT ?"); - sqlite3_bind_text(stmt, 1, "hello"); - affirm( "SELECT 'hello'".equals(sqlite3_expanded_sql(stmt)) ); + sqlite3_bind_text(stmt, 1, "hell😃"); + affirm( "SELECT 'hell😃'".equals(sqlite3_expanded_sql(stmt)) ); sqlite3_finalize(stmt); } diff --git a/manifest b/manifest index 8eaa3c8533..a0d778b788 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_sql()\sand\ssqlite3_expanded_sql()\sto\sJNI.\sStart\smarking\sC-side\sfunctions\swhich\swould\sneed\sexplicit\smutex\ssupport\sif\swe\sremove\s'synchronized'\sfrom\stheir\sJava\sentry\spoints\s(but\sthere\sare\smany\smore\sleft\sto\smark). -D 2023-08-11T18:04:53.620 +C Minor\sSQLTester\stest\stweaks\sand\shave\s'make\stester'\sinclude\sthe\sout-of-tree\stests\sif\sthey\sare\sfound. +D 2023-08-11T18:59:57.958 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,10 +231,10 @@ 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 d228f18de85e7f2f80e05edce3cc4f32da3c45a308e4e921807ca88279010871 +F ext/jni/GNUmakefile 329c441b44c9e91a33366f45eba0e9d0effc74161b9508c177b61f8d15486b19 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 478c168346b6ee0b600433c8d1b1c59b357fdd7418a1dd18498ce9e73742d685 +F ext/jni/src/c/sqlite3-jni.c 1b9fd402d507012b9c6f7f84f2a5998c77189048a893753790569b0329f76495 F ext/jni/src/c/sqlite3-jni.h 8f9e03016fea584627219eb4a14c6b1a6ba9fffbd7bc0df9e14220a6af8cfe18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -255,7 +255,7 @@ F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 23dec2c267ef1882bb0bfc12a2c771e13e091885942344abbde90861f7b795fa -F ext/jni/src/org/sqlite/jni/Tester1.java 7716949ae434308e698da505549e751478a03f40f2775d6b5486b754ded37a79 +F ext/jni/src/org/sqlite/jni/Tester1.java 62ba3f547800d6e35263f9e6caca9d82f6a087e6d02e1983d7acf5aab81115b6 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P afe190a940441de9bef8835c2dc6d278f861a772c3b7c7a2d399b2eabd4872e3 -R 2143ed5f6ccc929f2efe052657302ea3 +P c7fb32d1ef30d34449c3289c384ce33317c770927534af20d4b96fa385da40bc +R 5e9293a82d1730feb709386b92ddedeb U stephan -Z 2327e5c9841d1baf8b0f3e308e996b38 +Z d7446290cc2ea7450bd7ae0421832da3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3b128b9a84..d31fc5e06e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c7fb32d1ef30d34449c3289c384ce33317c770927534af20d4b96fa385da40bc \ No newline at end of file +6c8538d83495ce65dbd7417263b3b06dbbb2a649e9a61a743911944599d75ffc \ No newline at end of file From 783f5b5aa2f4b3990adc12bd551afd5d09c81ec9 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 11 Aug 2023 20:32:40 +0000 Subject: [PATCH 141/148] Fix a makefile deps problem which caused ext/jni build to fail if sqlite3.c/h were not created beforehand. FossilOrigin-Name: 101de670774f63757180282763730aa53e70198bd7a674c27e6044632d39d22a --- ext/jni/GNUmakefile | 12 ++++++------ ext/jni/src/c/sqlite3-jni.h | 2 +- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 02722d2955..34b07f9e8b 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -136,9 +136,9 @@ all: $(SQLite3Jni.class) # $(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 := $(firstword $(wildcard $(dir.src.c)/sqlite3.c $(dir.top)/sqlite3.c)) -sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h $(dir.top)/sqlite3.h)) -sqlite3.c ?= $(firstword $(wildcard $(dir.top)/sqlite3-see.c) $(sqlite3.canonical.c)) +sqlite3.canonical.c := $(firstword $(wildcard $(dir.src.c)/sqlite3.c) $(dir.top)/sqlite3.c) +sqlite3.canonical.h := $(firstword $(wildcard $(dir.src.c)/sqlite3.h) $(dir.top)/sqlite3.h) +sqlite3.c := $(sqlite3.canonical.c) sqlite3.h := $(sqlite3.canonical.h) #ifeq (,$(shell grep sqlite3_activate_see $(sqlite3.c) 2>/dev/null)) # SQLITE_C_IS_SEE := 0 @@ -152,7 +152,7 @@ $(sqlite3.h): $(MAKE) -C $(dir.top) sqlite3.c $(sqlite3.c): $(sqlite3.h) -SQLITE_OPT := \ +SQLITE_OPT = \ -DSQLITE_ENABLE_RTREE \ -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ -DSQLITE_ENABLE_STMTVTAB \ @@ -199,7 +199,7 @@ ifeq (1,$(enable.tester)) $(dir.bld.c)/org_sqlite_jni_tester_SQLTester.h: $(dir.src.jni.tester)/SQLTester.java endif #sqlite3-jni.dll.cfiles := $(dir.src.c) -sqlite3-jni.dll.cflags := \ +sqlite3-jni.dll.cflags = \ -fPIC \ -I. \ -I$(dir $(sqlite3.h)) \ @@ -223,7 +223,7 @@ $(sqlite3-jni.h): $(sqlite3-jni.h.in) $(MAKEFILE) $(sqlite3-jni.dll): $(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 $@ + $(sqlite3-jni.c) -shared -o $@ all: $(sqlite3-jni.dll) .PHONY: test diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index d8e75cd7ba..84e5d616c3 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1120,7 +1120,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 (JNIEnv *, jclass, jobject, jint, jint, jobject); /* diff --git a/manifest b/manifest index a0d778b788..62a3a3927c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\sSQLTester\stest\stweaks\sand\shave\s'make\stester'\sinclude\sthe\sout-of-tree\stests\sif\sthey\sare\sfound. -D 2023-08-11T18:59:57.958 +C Fix\sa\smakefile\sdeps\sproblem\swhich\scaused\sext/jni\sbuild\sto\sfail\sif\ssqlite3.c/h\swere\snot\screated\sbeforehand. +D 2023-08-11T20:32:40.205 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,11 +231,11 @@ 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 329c441b44c9e91a33366f45eba0e9d0effc74161b9508c177b61f8d15486b19 +F ext/jni/GNUmakefile 6a6633f768431bc1195c1b64bcec162069e3ed02442808eef9bd173c59ed0ddd F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 F ext/jni/src/c/sqlite3-jni.c 1b9fd402d507012b9c6f7f84f2a5998c77189048a893753790569b0329f76495 -F ext/jni/src/c/sqlite3-jni.h 8f9e03016fea584627219eb4a14c6b1a6ba9fffbd7bc0df9e14220a6af8cfe18 +F ext/jni/src/c/sqlite3-jni.h 965f78baf5643a87e74e8732714fe375225a55e5ff9a911aa7d08dd2a831e557 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -2090,8 +2090,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c7fb32d1ef30d34449c3289c384ce33317c770927534af20d4b96fa385da40bc -R 5e9293a82d1730feb709386b92ddedeb +P 6c8538d83495ce65dbd7417263b3b06dbbb2a649e9a61a743911944599d75ffc +R 00e08242dc8443215cfb70aa39f020b2 U stephan -Z d7446290cc2ea7450bd7ae0421832da3 +Z 4a14af7dfae41cdfaa811266b607e2bd # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index d31fc5e06e..a98896847a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6c8538d83495ce65dbd7417263b3b06dbbb2a649e9a61a743911944599d75ffc \ No newline at end of file +101de670774f63757180282763730aa53e70198bd7a674c27e6044632d39d22a \ No newline at end of file From 48dcdd3b740d71146d975d161d85b1fffe49e939 Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 11 Aug 2023 21:25:33 +0000 Subject: [PATCH 142/148] Add timing info to SQLTester. FossilOrigin-Name: b69b5facbf94e03e74d4a739ab85c5baac1c9ecbea8c330b2135d77e525b5d8a --- ext/jni/src/c/sqlite3-jni.h | 2 +- ext/jni/src/org/sqlite/jni/tester/SQLTester.java | 9 ++++++++- manifest | 14 +++++++------- manifest.uuid | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 84e5d616c3..d8e75cd7ba 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1120,7 +1120,7 @@ JNIEXPORT jstring JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1filename * Method: sqlite3_db_config * Signature: (Lorg/sqlite/jni/sqlite3;IILorg/sqlite/jni/OutputPointer/Int32;)I */ -JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2 +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2 (JNIEnv *, jclass, jobject, jint, jint, jobject); /* diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 15ac6c6a25..3143352b31 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -250,12 +250,14 @@ public class SQLTester { } public void runTests() throws Exception { + final long tStart = System.nanoTime(); for(String f : listInFiles){ reset(); ++nTestFile; final TestScript ts = new TestScript(f); outln(nextStartEmoji(), " starting [",f,"]"); boolean threw = false; + final long timeStart = System.nanoTime(); try{ ts.run(this); }catch(SQLTesterException e){ @@ -265,9 +267,14 @@ public class SQLTester { if( keepGoing ) outln("Continuing anyway becaure of the keep-going option."); else if( e.isFatal() ) throw e; }finally{ - outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ",ts.getFilename()); + final long timeEnd = System.nanoTime(); + outln("🏁",(threw ? "❌" : "✅")," ",nTest," test(s) in ", + ((timeEnd-timeStart)/1000000.0),"ms."); + //ts.getFilename()); } } + final long tEnd = System.nanoTime(); + outln("Total run-time: ",((tEnd-tStart)/1000000.0),"ms"); Util.unlink(initialDbName); } diff --git a/manifest b/manifest index 92f7518f85..092e3b47a6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\strunk\sinto\sjni\sbranch. -D 2023-08-11T21:24:08.276 +C Add\stiming\sinfo\sto\sSQLTester. +D 2023-08-11T21:25:33.877 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -235,7 +235,7 @@ F ext/jni/GNUmakefile 6a6633f768431bc1195c1b64bcec162069e3ed02442808eef9bd173c59 F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 F ext/jni/src/c/sqlite3-jni.c 1b9fd402d507012b9c6f7f84f2a5998c77189048a893753790569b0329f76495 -F ext/jni/src/c/sqlite3-jni.h 965f78baf5643a87e74e8732714fe375225a55e5ff9a911aa7d08dd2a831e557 +F ext/jni/src/c/sqlite3-jni.h 8f9e03016fea584627219eb4a14c6b1a6ba9fffbd7bc0df9e14220a6af8cfe18 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 5aa16634120dea614ebe3c127c69da87ba207cb658b09f600a8e8f9d2dc91e15 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 7ee74b162bab8558aa2888dcf77192e4ef6d262e2aaf5484f0337a2045144205 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 101de670774f63757180282763730aa53e70198bd7a674c27e6044632d39d22a 8a6b0c24937e855b710f97b4aea973eff53e6d43e1182842731547aa4b37db2a -R f0a8c88fe7b9660119e5c4d560adb197 +P 4f0aeeba0287e846908180eab6f7080ebe1323ebe49340771864d110e1ca5b2b +R 83650742b7c53ba16249ac5a897c9107 U stephan -Z b7f921f442de7d419e4b8b5ee576c27d +Z e72133113955550d8ba2a524328196a9 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0d1edcf329..355e8c53f9 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4f0aeeba0287e846908180eab6f7080ebe1323ebe49340771864d110e1ca5b2b \ No newline at end of file +b69b5facbf94e03e74d4a739ab85c5baac1c9ecbea8c330b2135d77e525b5d8a \ No newline at end of file From 238bea2ae6a7be903ccbb4d9eff2d8e5cff8c488 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 10:06:59 +0000 Subject: [PATCH 143/148] Bind sqlite3_status(64)() to JNI. FossilOrigin-Name: cefb6614e65ca1764ec72702f92f801382e63aa9b221fc9c68719d497e7499fd --- ext/jni/src/c/sqlite3-jni.c | 45 +++++++++++++++++----- ext/jni/src/c/sqlite3-jni.h | 16 ++++++++ ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 17 ++++---- ext/jni/src/org/sqlite/jni/Tester1.java | 44 +++++++++++++++------ manifest | 18 ++++----- manifest.uuid | 2 +- 6 files changed, 104 insertions(+), 38 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index fef6f0233c..3c7dac6310 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -310,7 +310,9 @@ static const struct { #define JBA_TOC(ARG) (*env)->GetByteArrayElements(env,ARG, NULL) #define JBA_RELEASE(ARG,VAR) if(VAR) (*env)->ReleaseByteArrayElements(env, ARG, VAR, JNI_ABORT) -/* Marker for code which needs(?) to be made thread-safe. */ +/* Marker for code which needs(?) to be made thread-safe. REASON is a + terse reminder about why that function requires a mutex. +*/ #define FIXME_THREADING(REASON) enum { @@ -1389,6 +1391,15 @@ static void OutputPointer_set_Int32(JNIEnv * const env, jobject const jOut, int EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int32.value"); } +/* Sets the value property of the OutputPointer.Int64 jOut object + to v. */ +static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ + jfieldID setter = 0; + setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); + (*env)->SetLongField(env, jOut, setter, v); + EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); +} + static void OutputPointer_set_sqlite3(JNIEnv * const env, jobject const jOut, jobject jDb){ jfieldID setter = 0; @@ -1408,14 +1419,6 @@ static void OutputPointer_set_sqlite3_stmt(JNIEnv * const env, jobject const jOu } #ifdef SQLITE_ENABLE_FTS5 -/* Sets the value property of the OutputPointer.Int64 jOut object - to v. */ -static void OutputPointer_set_Int64(JNIEnv * const env, jobject const jOut, jlong v){ - jfieldID setter = 0; - setupOutputPointer(env, S3JniClassNames.OutputPointer_Int64, "J", jOut, &setter); - (*env)->SetLongField(env, jOut, setter, v); - EXCEPTION_IS_FATAL("Cannot set OutputPointer.Int64.value"); -} #if 0 /* Sets the value property of the OutputPointer.ByteArray jOut object to v. */ @@ -3113,6 +3116,30 @@ JDECL(void,1set_1last_1insert_1rowid)(JENV_CSELF, jobject jpDb, jlong rowId){ (sqlite3_int64)rowId); } +FIXME_THREADING(nphCache) +JDECL(jint,1status)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset ){ + int iCur = 0, iHigh = 0; + int rc = sqlite3_status( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + +FIXME_THREADING(nphCache) +JDECL(jint,1status64)(JENV_CSELF, jint op, jobject jOutCurrent, jobject jOutHigh, + jboolean reset ){ + sqlite3_int64 iCur = 0, iHigh = 0; + int rc = sqlite3_status64( op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int64(env, jOutCurrent, iCur); + OutputPointer_set_Int64(env, jOutHigh, iHigh); + } + return (jint)rc; +} + static int s3jni_strlike_glob(int isLike, JNIEnv *const env, jbyteArray baG, jbyteArray baT, jint escLike){ int rc = 0; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index d8e75cd7ba..48720d0da8 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1419,6 +1419,22 @@ JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text JNIEXPORT void JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1result_1text64 (JNIEnv *, jclass, jobject, jbyteArray, jlong, jint); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status + * Signature: (ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_status64 + * Signature: (ILorg/sqlite/jni/OutputPointer/Int64;Lorg/sqlite/jni/OutputPointer/Int64;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1status64 + (JNIEnv *, jclass, jint, jobject, jobject, jboolean); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_rollback_hook diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 46488a40a9..d9508a9bad 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -937,14 +937,15 @@ public final class SQLite3Jni { ); - // public static synchronized native int sqlite3_status( - // int op, OutputPointer.Int32 pCurrent, OutputPointer.Int32 pHighwater, - // boolean reset - // ); - // public static synchronized native int sqlite3_status64( - // int op, OutputPointer.Int64 pCurrent, OutputPointer.Int64 pHighwater, - // boolean reset - // ); + public static synchronized native int sqlite3_status( + int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + + public static synchronized native int sqlite3_status64( + int op, @NotNull OutputPointer.Int64 pCurrent, + @NotNull OutputPointer.Int64 pHighwater, boolean reset + ); /** Sets the current UDF result to the given bytes, which are assumed diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index be1fa3edc2..1b9f766959 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -140,6 +140,17 @@ public class Tester1 { return rv; } + 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 testOpenDb1(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); @@ -155,17 +166,6 @@ public class Tester1 { affirm(0 == db.getNativePointer()); } - 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 testOpenDb2(){ final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open_v2(":memory:", out, @@ -488,6 +488,27 @@ public class Tester1 { affirm( 5 == ba.length /* as opposed to 6 in modified utf-8 */); } + private static void testStatus(){ + final OutputPointer.Int64 cur64 = new OutputPointer.Int64(); + final OutputPointer.Int64 high64 = new OutputPointer.Int64(); + final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); + final OutputPointer.Int32 high32 = new OutputPointer.Int32(); + final sqlite3 db = createNewDb(); + execSql(db, "create table t(a)"); + sqlite3_close_v2(db); + + int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.getValue() > 0 ); + affirm( high32.getValue() >= cur32.getValue() ); + + rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false); + affirm( 0 == rc ); + affirm( cur64.getValue() > 0 ); + affirm( high64.getValue() >= cur64.getValue() ); + + } + private static void testUdf1(){ final sqlite3 db = createNewDb(); // These ValueHolders are just to confirm that the func did what we want... @@ -1083,6 +1104,7 @@ public class Tester1 { testSql(); testCollation(); testToUtf8(); + testStatus(); testUdf1(); testUdfJavaObject(); testUdfAggregate(); diff --git a/manifest b/manifest index 092e3b47a6..a256199706 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\stiming\sinfo\sto\sSQLTester. -D 2023-08-11T21:25:33.877 +C Bind\ssqlite3_status(64)()\sto\sJNI. +D 2023-08-12T10:06:59.356 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,8 +234,8 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile 6a6633f768431bc1195c1b64bcec162069e3ed02442808eef9bd173c59ed0ddd F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 1b9fd402d507012b9c6f7f84f2a5998c77189048a893753790569b0329f76495 -F ext/jni/src/c/sqlite3-jni.h 8f9e03016fea584627219eb4a14c6b1a6ba9fffbd7bc0df9e14220a6af8cfe18 +F ext/jni/src/c/sqlite3-jni.c 5bb53c3f38486a79358737400d4e8a3ef66ae4ea58e7a5bd6e24a5816c2ad653 +F ext/jni/src/c/sqlite3-jni.h 7a51d1045ef78c2cb6929f6de5bd8242e9ebafe8abfe4a4255b67b62cfb4d2d5 F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -254,8 +254,8 @@ F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495 F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 23dec2c267ef1882bb0bfc12a2c771e13e091885942344abbde90861f7b795fa -F ext/jni/src/org/sqlite/jni/Tester1.java 62ba3f547800d6e35263f9e6caca9d82f6a087e6d02e1983d7acf5aab81115b6 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21af88949fd4c909d43a511f51e352f51dab34bbd5c42eba885709b7001dad03 +F ext/jni/src/org/sqlite/jni/Tester1.java c7bb4747c2df2aa52cd00bcaf15ed8609df121d80f2af75b05cbf0f673a8be42 F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 4f0aeeba0287e846908180eab6f7080ebe1323ebe49340771864d110e1ca5b2b -R 83650742b7c53ba16249ac5a897c9107 +P b69b5facbf94e03e74d4a739ab85c5baac1c9ecbea8c330b2135d77e525b5d8a +R b76030b5e4eb661dcf175e25776950a3 U stephan -Z e72133113955550d8ba2a524328196a9 +Z 02ed26d64128e71e5e4367d3108664ea # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 355e8c53f9..73cd2d3788 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b69b5facbf94e03e74d4a739ab85c5baac1c9ecbea8c330b2135d77e525b5d8a \ No newline at end of file +cefb6614e65ca1764ec72702f92f801382e63aa9b221fc9c68719d497e7499fd \ No newline at end of file From 613390680da4c9605a8e4f34c50c8f6c09915fe6 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 10:27:08 +0000 Subject: [PATCH 144/148] Bind sqlite3_db_status() to JNI. FossilOrigin-Name: b79477a0af94127b0638a8822de01156bef855a7e167f678809e1c978e1a0c3e --- ext/jni/src/c/sqlite3-jni.c | 182 ++++++++++-------- ext/jni/src/c/sqlite3-jni.h | 8 + ext/jni/src/org/sqlite/jni/OutputPointer.java | 8 +- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 6 +- ext/jni/src/org/sqlite/jni/Tester1.java | 20 +- manifest | 20 +- manifest.uuid | 2 +- 7 files changed, 140 insertions(+), 106 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index 3c7dac6310..e794e9c973 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2354,90 +2354,6 @@ JDECL(jboolean,1compileoption_1used)(JENV_CSELF, jstring name){ return rc; } -/* sqlite3_db_config() for (int,const char *) */ -JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( - JENV_CSELF, jobject jDb, jint op, jstring jStr -){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - int rc; - char *zStr; - - switch( (ps && jStr) ? op : 0 ){ - case SQLITE_DBCONFIG_MAINDBNAME: - zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); - if( zStr ){ - rc = sqlite3_db_config(ps->pDb, (int)op, zStr); - if( rc ){ - sqlite3_free( zStr ); - }else{ - sqlite3_free( ps->zMainDbName ); - ps->zMainDbName = zStr; - } - }else{ - rc = SQLITE_NOMEM; - } - break; - default: - rc = SQLITE_MISUSE; - } - return rc; -} - -FIXME_THREADING(perDb) -/* sqlite3_db_config() for (int,int*) */ -/* ACHTUNG: openjdk v19 creates a different mangled name for this - function than openjdk v8 does. */ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( - JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut -){ - S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); - int rc; - switch( ps ? op : 0 ){ - case SQLITE_DBCONFIG_ENABLE_FKEY: - case SQLITE_DBCONFIG_ENABLE_TRIGGER: - case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: - case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: - case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: - case SQLITE_DBCONFIG_ENABLE_QPSG: - case SQLITE_DBCONFIG_TRIGGER_EQP: - case SQLITE_DBCONFIG_RESET_DATABASE: - case SQLITE_DBCONFIG_DEFENSIVE: - case SQLITE_DBCONFIG_WRITABLE_SCHEMA: - case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: - case SQLITE_DBCONFIG_DQS_DML: - case SQLITE_DBCONFIG_DQS_DDL: - case SQLITE_DBCONFIG_ENABLE_VIEW: - case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: - case SQLITE_DBCONFIG_TRUSTED_SCHEMA: - case SQLITE_DBCONFIG_STMT_SCANSTATUS: - case SQLITE_DBCONFIG_REVERSE_SCANORDER: { - int pOut = 0; - rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut ); - if( 0==rc && jOut ){ - OutputPointer_set_Int32(env, jOut, pOut); - } - break; - } - default: - rc = SQLITE_MISUSE; - } - return (jint)rc; -} - -/** - This is a workaround for openjdk v19 (and possibly others) encoding - this function's name differently than JDK v8 does. If we do not - install both names for this function then Java will not be able to - find the function in both environments. -*/ -JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( - JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut -){ - return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( - env, jKlazz, jDb, op, onOff, jOut - ); -} - FIXME_THREADING(perDb) JDECL(jobject,1context_1db_1handle)(JENV_CSELF, jobject jpCx){ sqlite3 * const pDb = sqlite3_context_db_handle(PtrGet_sqlite3_context(jpCx)); @@ -2537,6 +2453,90 @@ JDECL(jint,1create_1function)(JENV_CSELF, jobject jDb, jstring jFuncName, return create_function(env, jDb, jFuncName, nArg, eTextRep, jFunctor); } +/* sqlite3_db_config() for (int,const char *) */ +JDECL(int,1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2)( + JENV_CSELF, jobject jDb, jint op, jstring jStr +){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc; + char *zStr; + + switch( (ps && jStr) ? op : 0 ){ + case SQLITE_DBCONFIG_MAINDBNAME: + zStr = s3jni_jstring_to_utf8(S3JniGlobal_env_cache(env), jStr, 0); + if( zStr ){ + rc = sqlite3_db_config(ps->pDb, (int)op, zStr); + if( rc ){ + sqlite3_free( zStr ); + }else{ + sqlite3_free( ps->zMainDbName ); + ps->zMainDbName = zStr; + } + }else{ + rc = SQLITE_NOMEM; + } + break; + default: + rc = SQLITE_MISUSE; + } + return rc; +} + +FIXME_THREADING(perDb) +/* sqlite3_db_config() for (int,int*) */ +/* ACHTUNG: openjdk v19 creates a different mangled name for this + function than openjdk v8 does. */ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); + int rc; + switch( ps ? op : 0 ){ + case SQLITE_DBCONFIG_ENABLE_FKEY: + case SQLITE_DBCONFIG_ENABLE_TRIGGER: + case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: + case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: + case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: + case SQLITE_DBCONFIG_ENABLE_QPSG: + case SQLITE_DBCONFIG_TRIGGER_EQP: + case SQLITE_DBCONFIG_RESET_DATABASE: + case SQLITE_DBCONFIG_DEFENSIVE: + case SQLITE_DBCONFIG_WRITABLE_SCHEMA: + case SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: + case SQLITE_DBCONFIG_DQS_DML: + case SQLITE_DBCONFIG_DQS_DDL: + case SQLITE_DBCONFIG_ENABLE_VIEW: + case SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: + case SQLITE_DBCONFIG_TRUSTED_SCHEMA: + case SQLITE_DBCONFIG_STMT_SCANSTATUS: + case SQLITE_DBCONFIG_REVERSE_SCANORDER: { + int pOut = 0; + rc = sqlite3_db_config( ps->pDb, (int)op, onOff, &pOut ); + if( 0==rc && jOut ){ + OutputPointer_set_Int32(env, jOut, pOut); + } + break; + } + default: + rc = SQLITE_MISUSE; + } + return (jint)rc; +} + +/** + This is a workaround for openjdk v19 (and possibly others) encoding + this function's name differently than JDK v8 does. If we do not + install both names for this function then Java will not be able to + find the function in both environments. +*/ +JDECL(jint,1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_00024Int32_2)( + JENV_CSELF, jobject jDb, jint op, jint onOff, jobject jOut +){ + return JFuncName(1db_1config__Lorg_sqlite_jni_sqlite3_2IILorg_sqlite_jni_OutputPointer_Int32_2)( + env, jKlazz, jDb, op, onOff, jOut + ); +} + JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ S3JniDb * const ps = S3JniDb_for_db(env, jDb, 0, 0); S3JniEnvCache * const jc = S3JniGlobal_env_cache(env); @@ -2558,6 +2558,20 @@ JDECL(jstring,1db_1filename)(JENV_CSELF, jobject jDb, jstring jDbName){ return jRv; } + +JDECL(jint,1db_1status)(JENV_CSELF, jobject jDb, jint op, jobject jOutCurrent, + jobject jOutHigh, jboolean reset ){ + int iCur = 0, iHigh = 0; + sqlite3 * const pDb = PtrGet_sqlite3(jDb); + int rc = sqlite3_db_status( pDb, op, &iCur, &iHigh, reset ); + if( 0==rc ){ + OutputPointer_set_Int32(env, jOutCurrent, iCur); + OutputPointer_set_Int32(env, jOutHigh, iHigh); + } + return (jint)rc; +} + + JDECL(jint,1errcode)(JENV_CSELF, jobject jpDb){ sqlite3 * const pDb = PtrGet_sqlite3(jpDb); return pDb ? sqlite3_errcode(pDb) : SQLITE_MISUSE; diff --git a/ext/jni/src/c/sqlite3-jni.h b/ext/jni/src/c/sqlite3-jni.h index 48720d0da8..bf2d5527b0 100644 --- a/ext/jni/src/c/sqlite3-jni.h +++ b/ext/jni/src/c/sqlite3-jni.h @@ -1131,6 +1131,14 @@ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_ JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1config__Lorg_sqlite_jni_sqlite3_2ILjava_lang_String_2 (JNIEnv *, jclass, jobject, jint, jstring); +/* + * Class: org_sqlite_jni_SQLite3Jni + * Method: sqlite3_db_status + * Signature: (Lorg/sqlite/jni/sqlite3;ILorg/sqlite/jni/OutputPointer/Int32;Lorg/sqlite/jni/OutputPointer/Int32;Z)I + */ +JNIEXPORT jint JNICALL Java_org_sqlite_jni_SQLite3Jni_sqlite3_1db_1status + (JNIEnv *, jclass, jobject, jint, jobject, jobject, jboolean); + /* * Class: org_sqlite_jni_SQLite3Jni * Method: sqlite3_errcode diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index c4cc21378d..9f505c7b4d 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -36,7 +36,7 @@ public final class OutputPointer { } public static final class Int32 { - private int value; + public int value; public Int32(){this(0);} public Int32(int v){value = v;} public final int getValue(){return value;} @@ -44,7 +44,7 @@ public final class OutputPointer { } public static final class Int64 { - private long value; + public long value; public Int64(){this(0);} public Int64(long v){value = v;} public final long getValue(){return value;} @@ -52,7 +52,7 @@ public final class OutputPointer { } public static final class String { - private java.lang.String value; + public java.lang.String value; public String(){this(null);} public String(java.lang.String v){value = v;} public final java.lang.String getValue(){return value;} @@ -60,7 +60,7 @@ public final class OutputPointer { } public static final class ByteArray { - private byte[] value; + public byte[] value; public ByteArray(){this(null);} public ByteArray(byte[] v){value = v;} public final byte[] getValue(){return value;} diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index d9508a9bad..7c4447b733 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -526,6 +526,11 @@ public final class SQLite3Jni { @NotNull sqlite3 db, int op, @NotNull String val ); + public static synchronized native int sqlite3_db_status( + @NotNull sqlite3 db, int op, @NotNull OutputPointer.Int32 pCurrent, + @NotNull OutputPointer.Int32 pHighwater, boolean reset + ); + public static synchronized native int sqlite3_errcode(@NotNull sqlite3 db); public static synchronized native String sqlite3_expanded_sql(@NotNull sqlite3_stmt stmt); @@ -936,7 +941,6 @@ public final class SQLite3Jni { long maxLength, int encoding ); - public static synchronized native int sqlite3_status( int op, @NotNull OutputPointer.Int32 pCurrent, @NotNull OutputPointer.Int32 pHighwater, boolean reset diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 1b9f766959..898d27be16 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -494,19 +494,27 @@ public class Tester1 { final OutputPointer.Int32 cur32 = new OutputPointer.Int32(); final OutputPointer.Int32 high32 = new OutputPointer.Int32(); final sqlite3 db = createNewDb(); - execSql(db, "create table t(a)"); - sqlite3_close_v2(db); + execSql(db, "create table t(a); insert into t values(1),(2),(3)"); int rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, cur32, high32, false); affirm( 0 == rc ); - affirm( cur32.getValue() > 0 ); - affirm( high32.getValue() >= cur32.getValue() ); + affirm( cur32.value > 0 ); + affirm( high32.value >= cur32.value ); rc = sqlite3_status64(SQLITE_STATUS_MEMORY_USED, cur64, high64, false); affirm( 0 == rc ); - affirm( cur64.getValue() > 0 ); - affirm( high64.getValue() >= cur64.getValue() ); + affirm( cur64.value > 0 ); + affirm( high64.value >= cur64.value ); + cur32.value = 0; + high32.value = 1; + rc = sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, cur32, high32, false); + affirm( 0 == rc ); + affirm( cur32.value > 0 ); + outln(cur32.value," ",high32.value); + affirm( high32.value == 0 /* always 0 for SCHEMA_USED */ ); + + sqlite3_close_v2(db); } private static void testUdf1(){ diff --git a/manifest b/manifest index a256199706..de527623e9 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_status(64)()\sto\sJNI. -D 2023-08-12T10:06:59.356 +C Bind\ssqlite3_db_status()\sto\sJNI. +D 2023-08-12T10:27:08.396 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,8 +234,8 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile 6a6633f768431bc1195c1b64bcec162069e3ed02442808eef9bd173c59ed0ddd F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c 5bb53c3f38486a79358737400d4e8a3ef66ae4ea58e7a5bd6e24a5816c2ad653 -F ext/jni/src/c/sqlite3-jni.h 7a51d1045ef78c2cb6929f6de5bd8242e9ebafe8abfe4a4255b67b62cfb4d2d5 +F ext/jni/src/c/sqlite3-jni.c e48ec95bc671cc281a5b442f6e80ea62b40947b6434ed07593bdd831b24ec979 +F ext/jni/src/c/sqlite3-jni.h c5f941b057a24ee62942e6e1bf5a7fd527e5004d20d9638e84a9382813c3cf2a F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 F ext/jni/src/org/sqlite/jni/BusyHandler.java 1b1d3e5c86cd796a0580c81b6af6550ad943baa25e47ada0dcca3aff3ebe978c @@ -249,13 +249,13 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java ebdd33d48064c3302d0d4a6dd345562a967f8420edad7c7509403be277d076a0 +F ext/jni/src/org/sqlite/jni/OutputPointer.java 05a34b408cdd7d482250b9fadd2e31fb01afc0e734f8d78f745bc70ea7889916 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 21af88949fd4c909d43a511f51e352f51dab34bbd5c42eba885709b7001dad03 -F ext/jni/src/org/sqlite/jni/Tester1.java c7bb4747c2df2aa52cd00bcaf15ed8609df121d80f2af75b05cbf0f673a8be42 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 18458d7419a9105e4987884f9a51a269a7aee3824abda861f937776a5dfd6b76 +F ext/jni/src/org/sqlite/jni/Tester1.java 26c380e5536a42fa95b755e106c958176ddd7916424a874dc88213b9b85757fc F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b69b5facbf94e03e74d4a739ab85c5baac1c9ecbea8c330b2135d77e525b5d8a -R b76030b5e4eb661dcf175e25776950a3 +P cefb6614e65ca1764ec72702f92f801382e63aa9b221fc9c68719d497e7499fd +R 17292eb9f1a8b03b2a02aeb82e00c48c U stephan -Z 02ed26d64128e71e5e4367d3108664ea +Z b2be796458128a6e289a1432b4828537 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 73cd2d3788..38ac7f19aa 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cefb6614e65ca1764ec72702f92f801382e63aa9b221fc9c68719d497e7499fd \ No newline at end of file +b79477a0af94127b0638a8822de01156bef855a7e167f678809e1c978e1a0c3e \ No newline at end of file From c216df75c62d18876292e78bfc6109569814f53c Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 10:39:26 +0000 Subject: [PATCH 145/148] Tweaks and docs for the OutputPointer family of Java classes. FossilOrigin-Name: 265c8fd0d4d425054f6bf7e9cb607ad2e0e46189f16c3014f7fdf9b650085497 --- ext/jni/src/org/sqlite/jni/OutputPointer.java | 57 +++++++++++++++++++ ext/jni/src/org/sqlite/jni/Tester1.java | 15 ++--- ext/jni/src/org/sqlite/jni/TesterFts5.java | 2 +- .../src/org/sqlite/jni/tester/SQLTester.java | 6 +- manifest | 18 +++--- manifest.uuid | 2 +- 6 files changed, 79 insertions(+), 21 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index 9f505c7b4d..9b9f2da3b2 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -21,21 +21,54 @@ package org.sqlite.jni; autoboxing at that level. */ public final class OutputPointer { + + /** + Output pointer for use with routines, such as sqlite3_open(), + which return a database handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ public static final class sqlite3 { private org.sqlite.jni.sqlite3 value; public sqlite3(){value = null;} public void clear(){value = null;} public final org.sqlite.jni.sqlite3 getValue(){return value;} + /** Equivalent to calling getValue() then clear(). */ + public final org.sqlite.jni.sqlite3 takeValue(){ + final org.sqlite.jni.sqlite3 v = value; + value = null; + return v; + } } + /** + Output pointer for use with routines, such as sqlite3_prepare(), + which return a statement handle via an output pointer. These + pointers can only be set by the JNI layer, not by client-level + code. + */ public static final class sqlite3_stmt { private org.sqlite.jni.sqlite3_stmt value; public sqlite3_stmt(){value = null;} public void clear(){value = null;} public final org.sqlite.jni.sqlite3_stmt getValue(){return value;} + /** Equivalent to calling getValue() then clear(). */ + public final org.sqlite.jni.sqlite3_stmt takeValue(){ + final org.sqlite.jni.sqlite3_stmt v = value; + value = null; + return v; + } } + /** + Output pointer for use with native routines which return integers via + output pointers. + */ public static final class Int32 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ public int value; public Int32(){this(0);} public Int32(int v){value = v;} @@ -43,7 +76,15 @@ public final class OutputPointer { public final void setValue(int v){value = v;} } + /** + Output pointer for use with native routines which return 64-bit integers + via output pointers. + */ public static final class Int64 { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ public long value; public Int64(){this(0);} public Int64(long v){value = v;} @@ -51,7 +92,15 @@ public final class OutputPointer { public final void setValue(long v){value = v;} } + /** + Output pointer for use with native routines which return strings via + output pointers. + */ public static final class String { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ public java.lang.String value; public String(){this(null);} public String(java.lang.String v){value = v;} @@ -59,7 +108,15 @@ public final class OutputPointer { public final void setValue(java.lang.String v){value = v;} } + /** + Output pointer for use with native routines which return byte + arrays via output pointers. + */ public static final class ByteArray { + /** + This is public for ease of use. Accessors are provided for + consistency with the higher-level types. + */ public byte[] value; public ByteArray(){this(null);} public ByteArray(byte[] v){value = v;} diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index 898d27be16..d073032a2b 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -69,14 +69,15 @@ public class Tester1 { final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; - sqlite3 db = out.getValue(); + sqlite3 db = out.takeValue(); if( 0!=rc ){ final String msg = db.getNativePointer()==0 ? sqlite3_errstr(rc) : sqlite3_errmsg(db); throw new RuntimeException("Opening db failed: "+msg); } - affirm(0 != db.getNativePointer()); + affirm( null == out.getValue() ); + affirm( 0 != db.getNativePointer() ); rc = sqlite3_busy_timeout(db, 2000); affirm( 0 == rc ); return db; @@ -102,8 +103,8 @@ public class Tester1 { rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); if(throwOnError) affirm(0 == rc); else if( 0!=rc ) break; - pos = oTail.getValue(); - stmt = outStmt.getValue(); + pos = oTail.value; + stmt = outStmt.takeValue(); if( null == stmt ){ // empty statement was parsed. continue; @@ -134,8 +135,8 @@ public class Tester1 { outStmt.clear(); int rc = sqlite3_prepare(db, sql, outStmt); affirm( 0 == rc ); - final sqlite3_stmt rv = outStmt.getValue(); - outStmt.clear(); + final sqlite3_stmt rv = outStmt.takeValue(); + affirm( null == outStmt.getValue() ); affirm( 0 != rv.getNativePointer() ); return rv; } @@ -208,7 +209,7 @@ public class Tester1 { rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); affirm(0 == rc); stmt = outStmt.getValue(); - pos = oTail.getValue(); + pos = oTail.value; /*outln("SQL tail pos = "+pos+". Chunk = "+ (new String(Arrays.copyOfRange(sqlChunk,0,pos), StandardCharsets.UTF_8)));*/ diff --git a/ext/jni/src/org/sqlite/jni/TesterFts5.java b/ext/jni/src/org/sqlite/jni/TesterFts5.java index 458ae1c410..6439768e29 100644 --- a/ext/jni/src/org/sqlite/jni/TesterFts5.java +++ b/ext/jni/src/org/sqlite/jni/TesterFts5.java @@ -49,7 +49,7 @@ public class TesterFts5 { for(int i = 0; i < nCols; ++i ){ int rc = ext.xColumnText(fCx, i, op); affirm( 0 == rc ); - final String val = op.getValue(); + final String val = op.value; affirm( val.equals(sqlite3_value_text(argv[i])) ); //outln("xFunction col "+i+": "+val); } diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index 3143352b31..c2fe618692 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -368,7 +368,7 @@ public class SQLTester { if( createIfNeeded ) flags |= SQLITE_OPEN_CREATE; final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open_v2(name, out, flags, null); - final sqlite3 db = out.getValue(); + final sqlite3 db = out.takeValue(); if( 0==rc && dbInitSql.length() > 0){ //outln("RUNNING DB INIT CODE: ",dbInitSql.toString()); rc = execSql(db, false, ResultBufferMode.NONE, @@ -518,8 +518,8 @@ public class SQLTester { } break; } - pos = oTail.getValue(); - stmt = outStmt.getValue(); + pos = oTail.value; + stmt = outStmt.takeValue(); if( null == stmt ){ // empty statement was parsed. continue; diff --git a/manifest b/manifest index de527623e9..7060ca1d4d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Bind\ssqlite3_db_status()\sto\sJNI. -D 2023-08-12T10:27:08.396 +C Tweaks\sand\sdocs\sfor\sthe\sOutputPointer\sfamily\sof\sJava\sclasses. +D 2023-08-12T10:39:26.077 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -249,14 +249,14 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java 05a34b408cdd7d482250b9fadd2e31fb01afc0e734f8d78f745bc70ea7889916 +F ext/jni/src/org/sqlite/jni/OutputPointer.java b4aa8137283336dabb4dcbed4b9f133d6cca3abe1babee0b5b0cfe698104f346 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 18458d7419a9105e4987884f9a51a269a7aee3824abda861f937776a5dfd6b76 -F ext/jni/src/org/sqlite/jni/Tester1.java 26c380e5536a42fa95b755e106c958176ddd7916424a874dc88213b9b85757fc -F ext/jni/src/org/sqlite/jni/TesterFts5.java cf2d687baafffdeba219b77cf611fd47a0556248820ea794ae3e8259bfbdc5ee +F ext/jni/src/org/sqlite/jni/Tester1.java 084694388797bf470dd4ab920082b4ca00f0bbb35ad6c024b7630c864b0dac65 +F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 7ee74b162bab8558aa2888dcf77192e4ef6d262e2aaf5484f0337a2045144205 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 26bb7e860174ec0ccc0303ee3626f0b8c8f47eb0bb1fbd37ad02b146794cfc58 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P cefb6614e65ca1764ec72702f92f801382e63aa9b221fc9c68719d497e7499fd -R 17292eb9f1a8b03b2a02aeb82e00c48c +P b79477a0af94127b0638a8822de01156bef855a7e167f678809e1c978e1a0c3e +R 12e2b2e0d37bf41e692cbd7292102259 U stephan -Z b2be796458128a6e289a1432b4828537 +Z 1871e1e7678c55928d63894731dc9434 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 38ac7f19aa..09e91e3893 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b79477a0af94127b0638a8822de01156bef855a7e167f678809e1c978e1a0c3e \ No newline at end of file +265c8fd0d4d425054f6bf7e9cb607ad2e0e46189f16c3014f7fdf9b650085497 \ No newline at end of file From 5b3a7543604a2216fae32494714d20c1631c8a6b Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 15:09:09 +0000 Subject: [PATCH 146/148] Further simplifications in the interface of the OutputPointer family of Java classes. FossilOrigin-Name: 962c3e0de2d64ab8a2bcf1a19f9c4224df3d15a41ac9f9b29da685be95c4ef7a --- ext/jni/src/org/sqlite/jni/OutputPointer.java | 28 +++++++++---------- ext/jni/src/org/sqlite/jni/Tester1.java | 28 +++++++++---------- .../src/org/sqlite/jni/tester/SQLTester.java | 6 ++-- manifest | 16 +++++------ manifest.uuid | 2 +- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index 9b9f2da3b2..f6524707df 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -32,9 +32,9 @@ public final class OutputPointer { private org.sqlite.jni.sqlite3 value; public sqlite3(){value = null;} public void clear(){value = null;} - public final org.sqlite.jni.sqlite3 getValue(){return value;} - /** Equivalent to calling getValue() then clear(). */ - public final org.sqlite.jni.sqlite3 takeValue(){ + public final org.sqlite.jni.sqlite3 get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.sqlite3 take(){ final org.sqlite.jni.sqlite3 v = value; value = null; return v; @@ -51,9 +51,9 @@ public final class OutputPointer { private org.sqlite.jni.sqlite3_stmt value; public sqlite3_stmt(){value = null;} public void clear(){value = null;} - public final org.sqlite.jni.sqlite3_stmt getValue(){return value;} - /** Equivalent to calling getValue() then clear(). */ - public final org.sqlite.jni.sqlite3_stmt takeValue(){ + public final org.sqlite.jni.sqlite3_stmt get(){return value;} + /** Equivalent to calling get() then clear(). */ + public final org.sqlite.jni.sqlite3_stmt take(){ final org.sqlite.jni.sqlite3_stmt v = value; value = null; return v; @@ -72,8 +72,8 @@ public final class OutputPointer { public int value; public Int32(){this(0);} public Int32(int v){value = v;} - public final int getValue(){return value;} - public final void setValue(int v){value = v;} + public final int get(){return value;} + public final void set(int v){value = v;} } /** @@ -88,8 +88,8 @@ public final class OutputPointer { public long value; public Int64(){this(0);} public Int64(long v){value = v;} - public final long getValue(){return value;} - public final void setValue(long v){value = v;} + public final long get(){return value;} + public final void set(long v){value = v;} } /** @@ -104,8 +104,8 @@ public final class OutputPointer { public java.lang.String value; public String(){this(null);} public String(java.lang.String v){value = v;} - public final java.lang.String getValue(){return value;} - public final void setValue(java.lang.String v){value = v;} + public final java.lang.String get(){return value;} + public final void set(java.lang.String v){value = v;} } /** @@ -120,7 +120,7 @@ public final class OutputPointer { public byte[] value; public ByteArray(){this(null);} public ByteArray(byte[] v){value = v;} - public final byte[] getValue(){return value;} - public final void setValue(byte[] v){value = v;} + public final byte[] get(){return value;} + public final void set(byte[] v){value = v;} } } diff --git a/ext/jni/src/org/sqlite/jni/Tester1.java b/ext/jni/src/org/sqlite/jni/Tester1.java index d073032a2b..ffe0b83846 100644 --- a/ext/jni/src/org/sqlite/jni/Tester1.java +++ b/ext/jni/src/org/sqlite/jni/Tester1.java @@ -69,14 +69,14 @@ public class Tester1 { final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; - sqlite3 db = out.takeValue(); + sqlite3 db = out.take(); if( 0!=rc ){ final String msg = db.getNativePointer()==0 ? sqlite3_errstr(rc) : sqlite3_errmsg(db); throw new RuntimeException("Opening db failed: "+msg); } - affirm( null == out.getValue() ); + affirm( null == out.get() ); affirm( 0 != db.getNativePointer() ); rc = sqlite3_busy_timeout(db, 2000); affirm( 0 == rc ); @@ -104,7 +104,7 @@ public class Tester1 { if(throwOnError) affirm(0 == rc); else if( 0!=rc ) break; pos = oTail.value; - stmt = outStmt.takeValue(); + stmt = outStmt.take(); if( null == stmt ){ // empty statement was parsed. continue; @@ -135,8 +135,8 @@ public class Tester1 { outStmt.clear(); int rc = sqlite3_prepare(db, sql, outStmt); affirm( 0 == rc ); - final sqlite3_stmt rv = outStmt.takeValue(); - affirm( null == outStmt.getValue() ); + final sqlite3_stmt rv = outStmt.take(); + affirm( null == outStmt.get() ); affirm( 0 != rv.getNativePointer() ); return rv; } @@ -156,7 +156,7 @@ public class Tester1 { final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open(":memory:", out); ++metrics.dbOpen; - sqlite3 db = out.getValue(); + sqlite3 db = out.get(); affirm(0 == rc); affirm(0 < db.getNativePointer()); sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, null) @@ -174,7 +174,7 @@ public class Tester1 { | SQLITE_OPEN_CREATE, null); ++metrics.dbOpen; affirm(0 == rc); - sqlite3 db = out.getValue(); + sqlite3 db = out.get(); affirm(0 < db.getNativePointer()); sqlite3_close_v2(db); affirm(0 == db.getNativePointer()); @@ -185,7 +185,7 @@ public class Tester1 { int rc; rc = sqlite3_prepare(db, "CREATE TABLE t1(a);", outStmt); affirm(0 == rc); - sqlite3_stmt stmt = outStmt.getValue(); + sqlite3_stmt stmt = outStmt.get(); affirm(0 != stmt.getNativePointer()); rc = sqlite3_step(stmt); affirm(SQLITE_DONE == rc); @@ -208,7 +208,7 @@ public class Tester1 { if( 0==sqlChunk.length ) break; rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); affirm(0 == rc); - stmt = outStmt.getValue(); + stmt = outStmt.get(); pos = oTail.value; /*outln("SQL tail pos = "+pos+". Chunk = "+ (new String(Arrays.copyOfRange(sqlChunk,0,pos), @@ -232,7 +232,7 @@ public class Tester1 { rc = sqlite3_prepare_v3(db, "INSERT INTO t2(a) VALUES(1),(2),(3)", SQLITE_PREPARE_NORMALIZE, outStmt); affirm(0 == rc); - stmt = outStmt.getValue(); + stmt = outStmt.get(); affirm(0 != stmt.getNativePointer()); sqlite3_finalize(stmt); affirm(0 == stmt.getNativePointer() ); @@ -797,13 +797,13 @@ public class Tester1 { int rc = sqlite3_open(dbName, outDb); ++metrics.dbOpen; affirm( 0 == rc ); - final sqlite3 db1 = outDb.getValue(); + final sqlite3 db1 = outDb.get(); execSql(db1, "CREATE TABLE IF NOT EXISTS t(a)"); rc = sqlite3_open(dbName, outDb); ++metrics.dbOpen; affirm( 0 == rc ); - affirm( outDb.getValue() != db1 ); - final sqlite3 db2 = outDb.getValue(); + affirm( outDb.get() != db1 ); + final sqlite3 db2 = outDb.get(); rc = sqlite3_db_config(db1, SQLITE_DBCONFIG_MAINDBNAME, "foo"); affirm( sqlite3_db_filename(db1, "foo").endsWith(dbName) ); @@ -826,7 +826,7 @@ public class Tester1 { affirm(!xDestroyed.value); rc = sqlite3_prepare_v2(db2, "SELECT * from t", outStmt); affirm( SQLITE_BUSY == rc); - assert( null == outStmt.getValue() ); + assert( null == outStmt.get() ); affirm( 3 == xBusyCalled.value ); sqlite3_close_v2(db1); affirm(!xDestroyed.value); diff --git a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java index c2fe618692..ffdb867d9b 100644 --- a/ext/jni/src/org/sqlite/jni/tester/SQLTester.java +++ b/ext/jni/src/org/sqlite/jni/tester/SQLTester.java @@ -368,7 +368,7 @@ public class SQLTester { if( createIfNeeded ) flags |= SQLITE_OPEN_CREATE; final OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); int rc = sqlite3_open_v2(name, out, flags, null); - final sqlite3 db = out.takeValue(); + final sqlite3 db = out.take(); if( 0==rc && dbInitSql.length() > 0){ //outln("RUNNING DB INIT CODE: ",dbInitSql.toString()); rc = execSql(db, false, ResultBufferMode.NONE, @@ -508,7 +508,7 @@ public class SQLTester { } if( 0==sqlChunk.length ) break; rc = sqlite3_prepare_v2(db, sqlChunk, outStmt, oTail); - /*outln("PREPARE rc ",rc," oTail=",oTail.getValue(),": ", + /*outln("PREPARE rc ",rc," oTail=",oTail.get(),": ", new String(sqlChunk,StandardCharsets.UTF_8),"\n");*/ if( 0!=rc ){ if(throwOnError){ @@ -519,7 +519,7 @@ public class SQLTester { break; } pos = oTail.value; - stmt = outStmt.takeValue(); + stmt = outStmt.take(); if( null == stmt ){ // empty statement was parsed. continue; diff --git a/manifest b/manifest index 7060ca1d4d..a5ad0a400c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Tweaks\sand\sdocs\sfor\sthe\sOutputPointer\sfamily\sof\sJava\sclasses. -D 2023-08-12T10:39:26.077 +C Further\ssimplifications\sin\sthe\sinterface\sof\sthe\sOutputPointer\sfamily\sof\sJava\sclasses. +D 2023-08-12T15:09:09.163 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -249,13 +249,13 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java b4aa8137283336dabb4dcbed4b9f133d6cca3abe1babee0b5b0cfe698104f346 +F ext/jni/src/org/sqlite/jni/OutputPointer.java b0adc42695f5c6d523f6b537b9eec7b5252581449d32a708aedfe323f6695407 F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 18458d7419a9105e4987884f9a51a269a7aee3824abda861f937776a5dfd6b76 -F ext/jni/src/org/sqlite/jni/Tester1.java 084694388797bf470dd4ab920082b4ca00f0bbb35ad6c024b7630c864b0dac65 +F ext/jni/src/org/sqlite/jni/Tester1.java 07c14a90427529ceba54b5e8344ca03602f5789dc53c4163ce22f92d8c577a11 F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d F ext/jni/src/org/sqlite/jni/UpdateHook.java e58645a1727f8a9bbe72dc072ec5b40d9f9362cb0aa24acfe93f49ff56a9016d @@ -267,7 +267,7 @@ F ext/jni/src/org/sqlite/jni/sqlite3.java 62b1b81935ccf3393472d17cb883dc5ff39c38 F ext/jni/src/org/sqlite/jni/sqlite3_context.java d26573fc7b309228cb49786e9078597d96232257defa955a3425d10897bca810 F ext/jni/src/org/sqlite/jni/sqlite3_stmt.java 78e6d1b95ac600a9475e9db4623f69449322b0c93d1bd4e1616e76ed547ed9fc F ext/jni/src/org/sqlite/jni/sqlite3_value.java 3d1d4903e267bc0bc81d57d21f5e85978eff389a1a6ed46726dbe75f85e6914a -F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 26bb7e860174ec0ccc0303ee3626f0b8c8f47eb0bb1fbd37ad02b146794cfc58 +F ext/jni/src/org/sqlite/jni/tester/SQLTester.java 1f1286428fab38dfefe328e72b5735f533b19af8dd17712dd3df7e044d21c8b8 F ext/jni/src/org/sqlite/jni/tester/test-script-interpreter.md f9f25126127045d051e918fe59004a1485311c50a13edbf18c79a6ff9160030e F ext/jni/src/tests/000-000-sanity.test cfe6dc1b950751d6096e3f5695becaadcdaa048bfe9567209d6eb676e693366d F ext/jni/src/tests/000-001-ignored.test e17e874c6ab3c437f1293d88093cf06286083b65bf162317f91bbfd92f961b70 @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P b79477a0af94127b0638a8822de01156bef855a7e167f678809e1c978e1a0c3e -R 12e2b2e0d37bf41e692cbd7292102259 +P 265c8fd0d4d425054f6bf7e9cb607ad2e0e46189f16c3014f7fdf9b650085497 +R 82a7bb87064debdd63f799e45e3a7aa0 U stephan -Z 1871e1e7678c55928d63894731dc9434 +Z 038e6a5cd04ee9dc715f1c7cadd72bf8 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 09e91e3893..ec186ef24c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -265c8fd0d4d425054f6bf7e9cb607ad2e0e46189f16c3014f7fdf9b650085497 \ No newline at end of file +962c3e0de2d64ab8a2bcf1a19f9c4224df3d15a41ac9f9b29da685be95c4ef7a \ No newline at end of file From 68522e16275acd2165dc9bd434700445929ca267 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 15:37:53 +0000 Subject: [PATCH 147/148] More JNI docs. FossilOrigin-Name: 290028d3ca5638f3bb18a0b243c7ba3c31c8a2b1a837c36ec29e0fc4ed6533f0 --- ext/jni/src/c/sqlite3-jni.c | 4 +- ext/jni/src/org/sqlite/jni/OutputPointer.java | 43 +++++++++++++++++- ext/jni/src/org/sqlite/jni/ResultCode.java | 44 ++++++++++--------- ext/jni/src/org/sqlite/jni/SQLite3Jni.java | 10 +++-- manifest | 18 ++++---- manifest.uuid | 2 +- 6 files changed, 84 insertions(+), 37 deletions(-) diff --git a/ext/jni/src/c/sqlite3-jni.c b/ext/jni/src/c/sqlite3-jni.c index e794e9c973..b28ea71144 100644 --- a/ext/jni/src/c/sqlite3-jni.c +++ b/ext/jni/src/c/sqlite3-jni.c @@ -2979,13 +2979,13 @@ JDECL(void,1result_1error)(JENV_CSELF, jobject jpCx, jbyteArray baMsg, switch(pjBuf ? eTextRep : SQLITE_UTF8){ case SQLITE_UTF8: { const char *zMsg = pjBuf ? (const char *)pjBuf : zUnspecified; - sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, baLen); + sqlite3_result_error(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); break; } case SQLITE_UTF16: { const void *zMsg = pjBuf ? (const void *)pjBuf : (const void *)zUnspecified; - sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, baLen); + sqlite3_result_error16(PtrGet_sqlite3_context(jpCx), zMsg, (int)baLen); break; } default: diff --git a/ext/jni/src/org/sqlite/jni/OutputPointer.java b/ext/jni/src/org/sqlite/jni/OutputPointer.java index f6524707df..82a90c9185 100644 --- a/ext/jni/src/org/sqlite/jni/OutputPointer.java +++ b/ext/jni/src/org/sqlite/jni/OutputPointer.java @@ -19,6 +19,23 @@ package org.sqlite.jni; We do not use a generic OutputPointer because working with those from the native JNI code is unduly quirky due to a lack of autoboxing at that level. + + The usage is similar for all of thes types: + + ``` + OutputPointer.sqlite3 out = new OutputPointer.sqlite3(); + assert( null==out.get() ); + int rc = sqlite3_open(":memory:", out); + if( 0!=rc ) ... error; + assert( null!=out.get() ); + sqlite3 db = out.take(); + assert( null==out.get() ); + ``` + + With the minor exception that the primitive types permit direct + access to the object's value via the `value` property, whereas the + JNI-level opaque types do not permit client-level code to set that + property. */ public final class OutputPointer { @@ -30,10 +47,13 @@ public final class OutputPointer { */ public static final class sqlite3 { private org.sqlite.jni.sqlite3 value; + //! Initializes with a null value. public sqlite3(){value = null;} + //! Sets the current value to null. public void clear(){value = null;} + //! Returns the current value. public final org.sqlite.jni.sqlite3 get(){return value;} - /** Equivalent to calling get() then clear(). */ + //! Equivalent to calling get() then clear(). public final org.sqlite.jni.sqlite3 take(){ final org.sqlite.jni.sqlite3 v = value; value = null; @@ -49,10 +69,13 @@ public final class OutputPointer { */ public static final class sqlite3_stmt { private org.sqlite.jni.sqlite3_stmt value; + //! Initializes with a null value. public sqlite3_stmt(){value = null;} + //! Sets the current value to null. public void clear(){value = null;} + //! Returns the current value. public final org.sqlite.jni.sqlite3_stmt get(){return value;} - /** Equivalent to calling get() then clear(). */ + //! Equivalent to calling get() then clear(). public final org.sqlite.jni.sqlite3_stmt take(){ final org.sqlite.jni.sqlite3_stmt v = value; value = null; @@ -70,9 +93,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public int value; + //! Initializes with the value 0. public Int32(){this(0);} + //! Initializes with the value v. public Int32(int v){value = v;} + //! Returns the current value. public final int get(){return value;} + //! Sets the current value to v. public final void set(int v){value = v;} } @@ -86,9 +113,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public long value; + //! Initializes with the value 0. public Int64(){this(0);} + //! Initializes with the value v. public Int64(long v){value = v;} + //! Returns the current value. public final long get(){return value;} + //! Sets the current value. public final void set(long v){value = v;} } @@ -102,9 +133,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public java.lang.String value; + //! Initializes with a null value. public String(){this(null);} + //! Initializes with the value v. public String(java.lang.String v){value = v;} + //! Returns the current value. public final java.lang.String get(){return value;} + //! Sets the current value. public final void set(java.lang.String v){value = v;} } @@ -118,9 +153,13 @@ public final class OutputPointer { consistency with the higher-level types. */ public byte[] value; + //! Initializes with the value null. public ByteArray(){this(null);} + //! Initializes with the value v. public ByteArray(byte[] v){value = v;} + //! Returns the current value. public final byte[] get(){return value;} + //! Sets the current value. public final void set(byte[] v){value = v;} } } diff --git a/ext/jni/src/org/sqlite/jni/ResultCode.java b/ext/jni/src/org/sqlite/jni/ResultCode.java index 30626f7564..0989bc744d 100644 --- a/ext/jni/src/org/sqlite/jni/ResultCode.java +++ b/ext/jni/src/org/sqlite/jni/ResultCode.java @@ -13,24 +13,11 @@ */ package org.sqlite.jni; -//! Internal level of indirection requires because we cannot reference -// static enum members from an enum constructor. -class ResultCodeMap { - private static final java.util.Map i2e - = new java.util.HashMap<>(); - - public static void set(int i, ResultCode src){ - i2e.put(i, src); - } - public static ResultCode get(int i){ - return i2e.get(i); - } -} - /** - This enum of sqlite3 result codes is provided not for use with the - C-style API (with which it won't work) but for higher-level code which - may find it useful to map codes to human-readable names. + This enum contains all of the core and "extended" result codes used + by the sqlite3 library. It is provided not for use with the C-style + API (with which it won't work) but for higher-level code which may + find it useful to map SQLite result codes to human-readable names. */ public enum ResultCode { SQLITE_OK(SQLite3Jni.SQLITE_OK), @@ -140,12 +127,29 @@ public enum ResultCode { public final int value; - ResultCode(int v){ - value = v; - ResultCodeMap.set(v, this); + ResultCode(int rc){ + value = rc; + ResultCodeMap.set(rc, this); } + /** + Returns the entry from this enum for the given result code, or + null if no match is found. + */ public static ResultCode getEntryForInt(int rc){ return ResultCodeMap.get(rc); } + + /** + Internal level of indirection required because we cannot initialize + static enum members in an enum before the enum constructor is + invoked. + */ + private static final class ResultCodeMap { + private static final java.util.Map i2e + = new java.util.HashMap<>(); + private static void set(int rc, ResultCode e){ i2e.put(rc, e); } + private static ResultCode get(int rc){ return i2e.get(rc); } + } + } diff --git a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java index 7c4447b733..9b2a176504 100644 --- a/ext/jni/src/org/sqlite/jni/SQLite3Jni.java +++ b/ext/jni/src/org/sqlite/jni/SQLite3Jni.java @@ -186,7 +186,9 @@ public final class SQLite3Jni { effects if invoked from within the execution of an auto-extension. i.e. auto extensions can neither be added, removed, nor cleared while one registered with this function is - running. + running. Auto-extensions registered directly with the library + via C code, as opposed to indirectly via Java, do not have that + limitation. See the AutoExtension class docs for more information. @@ -291,7 +293,8 @@ public final class SQLite3Jni { /** Works like the C API except that it returns false, without side - effects, if auto extensions are currently running. + effects, if auto extensions are currently running. (The JNI-level + list of extensions cannot be manipulated while it is being traversed.) */ public static synchronized native boolean sqlite3_cancel_auto_extension( @NotNull AutoExtension ax @@ -704,7 +707,8 @@ public final class SQLite3Jni { /** Works like the C API except that it has no side effects if auto - extensions are currently running. + extensions are currently running. (The JNI-level list of + extensions cannot be manipulated while it is being traversed.) */ public static synchronized native void sqlite3_reset_auto_extension(); diff --git a/manifest b/manifest index a5ad0a400c..d97b74319d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Further\ssimplifications\sin\sthe\sinterface\sof\sthe\sOutputPointer\sfamily\sof\sJava\sclasses. -D 2023-08-12T15:09:09.163 +C More\sJNI\sdocs. +D 2023-08-12T15:37:53.147 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -234,7 +234,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3 F ext/jni/GNUmakefile 6a6633f768431bc1195c1b64bcec162069e3ed02442808eef9bd173c59ed0ddd F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 -F ext/jni/src/c/sqlite3-jni.c e48ec95bc671cc281a5b442f6e80ea62b40947b6434ed07593bdd831b24ec979 +F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 F ext/jni/src/c/sqlite3-jni.h c5f941b057a24ee62942e6e1bf5a7fd527e5004d20d9638e84a9382813c3cf2a F ext/jni/src/org/sqlite/jni/Authorizer.java 1308988f7f40579ea0e4deeaec3c6be971630566bd021c31367fe3f5140db892 F ext/jni/src/org/sqlite/jni/AutoExtension.java 18e83f6f463e306df60b2dceb65247d32af1f78af4bbbae9155411a8c6cdb093 @@ -249,12 +249,12 @@ F ext/jni/src/org/sqlite/jni/Fts5Function.java 65cde7151e441fee012250a5e03277de7 F ext/jni/src/org/sqlite/jni/Fts5PhraseIter.java 6642beda341c0b1b46af4e2d7f6f9ab03a7aede43277b2c92859176d6bce3be9 F ext/jni/src/org/sqlite/jni/Fts5Tokenizer.java 91489893596b6528c0df5cd7180bd5b55809c26e2b797fb321dfcdbc1298c060 F ext/jni/src/org/sqlite/jni/NativePointerHolder.java 9c5d901cce4f7e57c3d623f4e2476f9f79a8eed6e51b2a603f37866018e040ee -F ext/jni/src/org/sqlite/jni/OutputPointer.java b0adc42695f5c6d523f6b537b9eec7b5252581449d32a708aedfe323f6695407 +F ext/jni/src/org/sqlite/jni/OutputPointer.java d81f8bd43d2296ae373692370cfad16ddde76f5c14cd2760f7b4e1113ef56d4c F ext/jni/src/org/sqlite/jni/ProgressHandler.java 6f62053a828a572de809828b1ee495380677e87daa29a1c57a0e2c06b0a131dc -F ext/jni/src/org/sqlite/jni/ResultCode.java 7cdf993f2037ab7bd244c9a34dbaef2ace3beb5da5d7e7fda5c6f67634ceb647 +F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 F ext/jni/src/org/sqlite/jni/RollbackHook.java b04c8abcc6ade44a8a57129e33765793f69df0ba909e49ba18d73f4268d92564 F ext/jni/src/org/sqlite/jni/SQLFunction.java 09ce81c1c637e31c3a830d4c859cce95d65f5e02ff45f8bd1985b3479381bc46 -F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 18458d7419a9105e4987884f9a51a269a7aee3824abda861f937776a5dfd6b76 +F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 4b6fd22e04e63eb65d8e4e38fda39ecf15ce244d034607517627ce2e766e7e65 F ext/jni/src/org/sqlite/jni/Tester1.java 07c14a90427529ceba54b5e8344ca03602f5789dc53c4163ce22f92d8c577a11 F ext/jni/src/org/sqlite/jni/TesterFts5.java 59e22dd24af033ea8827d36225a2f3297908fb6af8818ead8850c6c6847557b1 F ext/jni/src/org/sqlite/jni/Tracer.java a5cece9f947b0af27669b8baec300b6dd7ff859c3e6a6e4a1bd8b50f9714775d @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 265c8fd0d4d425054f6bf7e9cb607ad2e0e46189f16c3014f7fdf9b650085497 -R 82a7bb87064debdd63f799e45e3a7aa0 +P 962c3e0de2d64ab8a2bcf1a19f9c4224df3d15a41ac9f9b29da685be95c4ef7a +R 26b1f361ecc1f9ee890a179908638933 U stephan -Z 038e6a5cd04ee9dc715f1c7cadd72bf8 +Z c7ff43bec79fef52fa552298593f1614 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index ec186ef24c..1c45e63bd3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -962c3e0de2d64ab8a2bcf1a19f9c4224df3d15a41ac9f9b29da685be95c4ef7a \ No newline at end of file +290028d3ca5638f3bb18a0b243c7ba3c31c8a2b1a837c36ec29e0fc4ed6533f0 \ No newline at end of file From d8a94dbb368d7099a5a5a26c30ea1fa58df6a348 Mon Sep 17 00:00:00 2001 From: stephan Date: Sat, 12 Aug 2023 20:50:29 +0000 Subject: [PATCH 148/148] Correct JNI .jar rules to only include *.java/class files, not *.* (*~ files). FossilOrigin-Name: 1ba7754045a009d9c94b23ac76b9bb8d9c9cb24d42dcdf1203ee75ac85765d3e --- ext/jni/GNUmakefile | 2 +- manifest | 12 ++++++------ manifest.uuid | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ext/jni/GNUmakefile b/ext/jni/GNUmakefile index 34b07f9e8b..36ef42d31a 100644 --- a/ext/jni/GNUmakefile +++ b/ext/jni/GNUmakefile @@ -271,7 +271,7 @@ tests: test tester package.jar.in := $(abspath $(dir.src)/jar.in) CLEAN_FILES += $(package.jar.in) $(package.jar.in): $(MAKEFILE) $(CLASS_FILES.main) - cd $(dir.src); ls -1 org/sqlite/jni/*.* > $@ + cd $(dir.src); ls -1 org/sqlite/jni/*.java org/sqlite/jni/*.class > $@ @ls -la $@ @echo "To use this jar you will need the -Djava.library.path=DIR/WITH/libsqlite3-jni.so flag." @echo "e.g. java -jar $@ -Djava.library.path=bld" diff --git a/manifest b/manifest index d97b74319d..5886fc5e4c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C More\sJNI\sdocs. -D 2023-08-12T15:37:53.147 +C Correct\sJNI\s.jar\srules\sto\sonly\sinclude\s*.java/class\sfiles,\snot\s*.*\s(*~\sfiles). +D 2023-08-12T20:50:29.873 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -231,7 +231,7 @@ 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 6a6633f768431bc1195c1b64bcec162069e3ed02442808eef9bd173c59ed0ddd +F ext/jni/GNUmakefile 435485ff2005c4bcdea808f5efe6d4ee66a00430c2499dcc4927b20378486bea F ext/jni/README.md 7a614a2fa6c561205f7a53fd8626cf93a7b5711ff454fc1814517f796df398eb F ext/jni/jar-dist.make f90a553203a57934bf275bed86479485135a52f48ac5c1cfe6499ae07b0b35a4 F ext/jni/src/c/sqlite3-jni.c bea6b8691a5fa3a8626a771757bb261208d3c5fc6598266d3b0ee23d88e35632 @@ -2091,8 +2091,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 962c3e0de2d64ab8a2bcf1a19f9c4224df3d15a41ac9f9b29da685be95c4ef7a -R 26b1f361ecc1f9ee890a179908638933 +P 290028d3ca5638f3bb18a0b243c7ba3c31c8a2b1a837c36ec29e0fc4ed6533f0 +R adb17bd749dd5a1323c629b30f4e78d5 U stephan -Z c7ff43bec79fef52fa552298593f1614 +Z 783b680c88e3a138aa5797daa96c621c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 1c45e63bd3..a89e4b4b29 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -290028d3ca5638f3bb18a0b243c7ba3c31c8a2b1a837c36ec29e0fc4ed6533f0 \ No newline at end of file +1ba7754045a009d9c94b23ac76b9bb8d9c9cb24d42dcdf1203ee75ac85765d3e \ No newline at end of file