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

Bind collation and collation-needed to JNI wrapper1 and correct the callback return type for collation-needed callbacks in the lower-level JNI binding.

FossilOrigin-Name: 0f673140681685ab390ecd7326a8b80d060b7ab23c31a2cfc28ba76fd5096afe
This commit is contained in:
stephan
2023-11-04 23:37:11 +00:00
parent dc8a684c11
commit 15d38c0dde
7 changed files with 176 additions and 17 deletions

View File

@ -2817,7 +2817,7 @@ S3JniApi(sqlite3_collation_needed(),jint,1collation_1needed)(
}else{
jclass const klazz = (*env)->GetObjectClass(env, jHook);
jmethodID const xCallback = (*env)->GetMethodID(
env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)I"
env, klazz, "call", "(Lorg/sqlite/jni/capi/sqlite3;ILjava/lang/String;)V"
);
S3JniUnrefLocal(klazz);
S3JniIfThrew {

View File

@ -21,8 +21,9 @@ public interface CollationNeededCallback extends CallbackProxy {
Has the same semantics as the C-level sqlite3_create_collation()
callback.
<p>If it throws, the exception message is passed on to the db and
the exception is suppressed.
<p>Because the C API has no mechanism for reporting errors
from this callbacks, any exceptions thrown by this callback
are suppressed.
*/
int call(sqlite3 db, int eTextRep, String collationName);
void call(sqlite3 db, int eTextRep, String collationName);
}

View File

@ -593,9 +593,9 @@ public class Tester1 implements Runnable {
};
final CollationNeededCallback collLoader = new CollationNeededCallback(){
@Override
public int call(sqlite3 dbArg, int eTextRep, String collationName){
public void call(sqlite3 dbArg, int eTextRep, String collationName){
affirm(dbArg == db/* as opposed to a temporary object*/);
return sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
sqlite3_create_collation(dbArg, "reversi", eTextRep, myCollation);
}
};
int rc = sqlite3_collation_needed(db, collLoader);

View File

@ -100,6 +100,13 @@ public final class Sqlite implements AutoCloseable {
public static final int DBCONFIG_STMT_SCANSTATUS = CApi.SQLITE_DBCONFIG_STMT_SCANSTATUS;
public static final int DBCONFIG_REVERSE_SCANORDER = CApi.SQLITE_DBCONFIG_REVERSE_SCANORDER;
public static final int UTF8 = CApi.SQLITE_UTF8;
public static final int UTF16 = CApi.SQLITE_UTF16;
public static final int UTF16LE = CApi.SQLITE_UTF16LE;
public static final int UTF16BE = CApi.SQLITE_UTF16BE;
/* We elide the UTF16_ALIGNED from this interface because it
is irrelevant for the Java interface. */
//! Used only by the open() factory functions.
private Sqlite(sqlite3 db){
this.db = db;
@ -1122,4 +1129,94 @@ public final class Sqlite implements AutoCloseable {
return new Backup(this, schemaDest, dbSrc, schemaSrc);
}
/**
Callback type for use with createCollation().
*/
public interface Collation {
/**
Called by the SQLite core to compare inputs. Implementations
must compare its two arguments using memcmp(3) semantics.
Warning: the SQLite core has no mechanism for reporting errors
from custom collations and its workflow does not accommodate
propagation of exceptions from callbacks. Any exceptions thrown
from collations will be silently supressed and sorting results
will be unpredictable.
*/
int call(byte[] lhs, byte[] rhs);
}
/**
Analog to sqlite3_create_collation().
Throws if name is null or empty, c is null, or the encoding flag
is invalid. The encoding must be one of the UTF8, UTF16, UTF16LE,
or UTF16BE constants.
*/
public void createCollation(String name, int encoding, Collation c){
thisDb();
if( null==name || 0==name.length()){
throw new IllegalArgumentException("Collation name may not be null or empty.");
}
if( null==c ){
throw new IllegalArgumentException("Collation may not be null.");
}
switch(encoding){
case UTF8:
case UTF16:
case UTF16LE:
case UTF16BE:
break;
default:
throw new IllegalArgumentException("Invalid Collation encoding.");
}
checkRc(
CApi.sqlite3_create_collation(
thisDb(), name, encoding, new org.sqlite.jni.capi.CollationCallback(){
@Override public int call(byte[] lhs, byte[] rhs){
try{return c.call(lhs, rhs);}
catch(Exception e){return 0;}
}
@Override public void xDestroy(){}
}
)
);
}
/**
Callback for use with onCollationNeeded().
*/
public interface CollationNeeded {
/**
Must behave as documented for the callback for
sqlite3_collation_needed().
Warning: the C API has no mechanism for reporting or
propagating errors from this callback, so any exceptions it
throws are suppressed.
*/
void call(Sqlite db, int encoding, String collationName);
}
/**
Sets up the given object to be called by the SQLite core when it
encounters a collation name which it does not know. Pass a null
object to disconnect the object from the core. This replaces any
existing collation-needed loader, or is a no-op if the given
object is already registered. Throws if registering the loader
fails.
*/
public void onCollationNeeded( CollationNeeded cn ){
org.sqlite.jni.capi.CollationNeededCallback cnc = null;
if( null!=cn ){
cnc = new org.sqlite.jni.capi.CollationNeededCallback(){
@Override public void call(sqlite3 db, int encoding, String collationName){
final Sqlite xdb = Sqlite.fromNative(db);
if(null!=xdb) cn.call(xdb, encoding, collationName);
}
};
}
checkRc( CApi.sqlite3_collation_needed(thisDb(), cnc) );
}
}

View File

@ -638,6 +638,67 @@ public class Tester2 implements Runnable {
dbDest.close();
}
private void testCollation(){
final Sqlite db = openDb();
execSql(db, "CREATE TABLE t(a); INSERT INTO t(a) VALUES('a'),('b'),('c')");
final Sqlite.Collation myCollation = new Sqlite.Collation() {
private String myState =
"this is local state. There is much like it, but this is mine.";
@Override
// Reverse-sorts its inputs...
public int call(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;
}
};
final Sqlite.CollationNeeded collLoader = new Sqlite.CollationNeeded(){
@Override
public void call(Sqlite dbArg, int eTextRep, String collationName){
affirm(dbArg == db);
db.createCollation("reversi", eTextRep, myCollation);
}
};
db.onCollationNeeded(collLoader);
Sqlite.Stmt stmt = db.prepare("SELECT a FROM t ORDER BY a COLLATE reversi");
int counter = 0;
while( stmt.step() ){
final String val = stmt.columnText16(0);
++counter;
switch(counter){
case 1: affirm("c".equals(val)); break;
case 2: affirm("b".equals(val)); break;
case 3: affirm("a".equals(val)); break;
}
}
affirm(3 == counter);
stmt.finalizeStmt();
stmt = db.prepare("SELECT a FROM t ORDER BY a");
counter = 0;
while( stmt.step() ){
final String val = stmt.columnText16(0);
++counter;
//outln("Non-REVERSI'd row#"+counter+": "+val);
switch(counter){
case 3: affirm("c".equals(val)); break;
case 2: affirm("b".equals(val)); break;
case 1: affirm("a".equals(val)); break;
}
}
affirm(3 == counter);
stmt.finalizeStmt();
db.onCollationNeeded(null);
db.close();
}
private void runTests(boolean fromThread) throws Exception {
List<java.lang.reflect.Method> mlist = testMethods;
affirm( null!=mlist );