]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add a way to convert from standard UTF-8 to a Java string (JNI lacks this capability).
authorstephan <stephan@noemail.net>
Sun, 6 Aug 2023 10:49:47 +0000 (10:49 +0000)
committerstephan <stephan@noemail.net>
Sun, 6 Aug 2023 10:49:47 +0000 (10:49 +0000)
FossilOrigin-Name: 07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262

ext/jni/src/c/sqlite3-jni.c
ext/jni/src/org/sqlite/jni/Tester1.java
manifest
manifest.uuid

index 646ecab06c2e1bd00b1f457ebd2ac4a84254e4c6..377c0e3a9c67c0eeef35c2092da7e5105919f503 100644 (file)
@@ -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,
-                                       "<init>", "(J)V");
+  row->g.ctorLong1 = (*env)->GetMethodID(env, row->g.cLong,
+                                         "<init>", "(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,
                         "<init>", "([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";
index 334484166cc46d68b2227e01c99b3ac3897911b5..2d1dcd4be8dd9cbc01810a9dcd73771801f54dd9 100644 (file)
@@ -683,6 +683,10 @@ public class Tester1 {
   private static void testTrace(){
     final sqlite3 db = createNewDb();
     final ValueHolder<Integer> 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 );
index bc94970f537fb6ba83c8505fc68db8f2fc85a049..5cc0301b263adafd1cb01eadcd98b91654fb2b76 100644 (file)
--- 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.
index 6a5c31e951d2a89cfa8b8894ef93b84b80ca5c88..ba355282207b6b3ae09052c8ac3bd67806fd94ef 100644 (file)
@@ -1 +1 @@
-586720fa714ac74491cd85d0c6645242e55e5989ad312ef6e15e0b0acc6906ff
\ No newline at end of file
+07dd082c9e371829a18aeb574f842891e545e1fc125760238ede7e7e2b6a4262
\ No newline at end of file