1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

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

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

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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