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:
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) );
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
|
Reference in New Issue
Block a user