From e3a5799f2abe7b69e8f786f2bb079139dcd21438 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:26:25 +0000 Subject: [PATCH 1/2] chore: bump jni from 0.21.1 to 0.22.4 Bumps [jni](https://github.com/jni-rs/jni-rs) from 0.21.1 to 0.22.4. - [Release notes](https://github.com/jni-rs/jni-rs/releases) - [Changelog](https://github.com/jni-rs/jni-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jni-rs/jni-rs/compare/v0.21.1...v0.22.4) --- updated-dependencies: - dependency-name: jni dependency-version: 0.22.4 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 81 ++++++++++++++++++---------------- prqlc/bindings/java/Cargo.toml | 2 +- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34337301b47e..87d843072328 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,12 +567,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -2087,25 +2081,52 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", + "jni-macros", "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] [[package]] name = "jobserver" @@ -3936,6 +3957,16 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + [[package]] name = "simdutf8" version = "0.1.5" @@ -5142,15 +5173,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -5178,21 +5200,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" diff --git a/prqlc/bindings/java/Cargo.toml b/prqlc/bindings/java/Cargo.toml index 61053f667acf..b4eee0769724 100644 --- a/prqlc/bindings/java/Cargo.toml +++ b/prqlc/bindings/java/Cargo.toml @@ -16,7 +16,7 @@ doctest = false test = false [dependencies] -jni = "0.21.1" +jni = "0.22.4" prqlc = {path = "../../prqlc", default-features = false} [package.metadata.release] From 4fedae95f18f12dbb8c854ea629326b285afa7ec Mon Sep 17 00:00:00 2001 From: prql-bot <107324867+prql-bot@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:41:38 +0000 Subject: [PATCH 2/2] fix: migrate Java bindings to the jni 0.22 environment API jni 0.22 replaces the `JNIEnv` native-method argument with `EnvUnowned`, which exposes no JNI methods; the methods now live on `Env`, reachable only inside an `EnvUnowned::with_env` closure whose outcome is mapped through an `ErrorPolicy`. `jni_sys::jboolean` also became `bool` rather than `u8`, so the `!= 0` conversions no longer type-check. Java-visible behavior is unchanged: an unknown target dialect still throws IllegalArgumentException, a compile error still throws java.lang.Exception, and both paths still return a null reference (`JString::default()` is `JObject::null()`). Panics inside the compiler are now caught by `with_env` and converted to a Java exception rather than unwinding out of an `extern "system"` function. Adds a test for the IllegalArgumentException path, which the migration re-implements by hand. --- .../org/prql/prql4j/PrqlCompilerTest.java | 5 + prqlc/bindings/java/src/lib.rs | 169 +++++++++--------- 2 files changed, 93 insertions(+), 81 deletions(-) diff --git a/prqlc/bindings/java/java/src/test/java/org/prql/prql4j/PrqlCompilerTest.java b/prqlc/bindings/java/java/src/test/java/org/prql/prql4j/PrqlCompilerTest.java index c2767bcacb28..82d079ee6d5b 100644 --- a/prqlc/bindings/java/java/src/test/java/org/prql/prql4j/PrqlCompilerTest.java +++ b/prqlc/bindings/java/java/src/test/java/org/prql/prql4j/PrqlCompilerTest.java @@ -22,4 +22,9 @@ public void compile() throws Exception { public void compileWithError() throws Exception { PrqlCompiler.toSql("from table | filter id >> 1", "sql.mysql", true, true); } + + @Test(expected = IllegalArgumentException.class) + public void compileWithUnknownTarget() throws Exception { + PrqlCompiler.toSql("from my_table", "sql.not_a_dialect", true, true); + } } diff --git a/prqlc/bindings/java/src/lib.rs b/prqlc/bindings/java/src/lib.rs index c3d33a801a7f..400f95acbb3b 100644 --- a/prqlc/bindings/java/src/lib.rs +++ b/prqlc/bindings/java/src/lib.rs @@ -1,114 +1,121 @@ use std::str::FromStr; +use jni::errors::{Error as JniError, Result as JniResult, ThrowRuntimeExAndDefault}; use jni::objects::{JClass, JString}; -use jni::sys::{jboolean, jstring}; -use jni::JNIEnv; +use jni::strings::JNIString; +use jni::sys::jboolean; +use jni::{jni_str, Env, EnvUnowned}; use prqlc::{json, pl_to_prql, prql_to_pl, ErrorMessages, Options, Target}; #[no_mangle] #[allow(non_snake_case)] -pub extern "system" fn Java_org_prql_prql4j_PrqlCompiler_toSql( - mut env: JNIEnv, - _class: JClass, - query: JString, - target: JString, +pub extern "system" fn Java_org_prql_prql4j_PrqlCompiler_toSql<'local>( + mut env: EnvUnowned<'local>, + _class: JClass<'local>, + query: JString<'local>, + target: JString<'local>, format: jboolean, signature: jboolean, -) -> jstring { - let prql_query = match jstring_to_string(&mut env, &query, "query") { - Some(s) => s, - None => return std::ptr::null_mut(), - }; - let target_str = match jstring_to_string(&mut env, &target, "target") { - Some(s) => s, - None => return std::ptr::null_mut(), - }; - let prql_dialect = match Target::from_str(&target_str) { - Ok(t) => t, - Err(e) => { - throw_illegal_argument(&mut env, &format!("invalid target dialect: {e}")); - return std::ptr::null_mut(); - } - }; - let opt = Options { - format: format != 0, - target: prql_dialect, - signature_comment: signature != 0, - // TODO: add support for `display` - ..Default::default() - }; - let result = prqlc::compile(&prql_query, &opt); - java_string_with_exception(result, &mut env) +) -> JString<'local> { + env.with_env(|env| -> JniResult> { + let prql_query = jstring_to_string(env, &query, "query")?; + let target_str = jstring_to_string(env, &target, "target")?; + let prql_dialect = match Target::from_str(&target_str) { + Ok(t) => t, + Err(e) => { + return Err(throw_illegal_argument( + env, + &format!("invalid target dialect: {e}"), + )) + } + }; + let opt = Options { + format, + target: prql_dialect, + signature_comment: signature, + // TODO: add support for `display` + ..Default::default() + }; + let result = prqlc::compile(&prql_query, &opt); + java_string_with_exception(result, env) + }) + .resolve::() } #[no_mangle] #[allow(non_snake_case)] -pub extern "system" fn Java_org_prql_prql4j_PrqlCompiler_format( - mut env: JNIEnv, - _class: JClass, - query: JString, -) -> jstring { - let prql_query = match jstring_to_string(&mut env, &query, "query") { - Some(s) => s, - None => return std::ptr::null_mut(), - }; - let result = prql_to_pl(&prql_query).and_then(|x| pl_to_prql(&x)); - java_string_with_exception(result, &mut env) +pub extern "system" fn Java_org_prql_prql4j_PrqlCompiler_format<'local>( + mut env: EnvUnowned<'local>, + _class: JClass<'local>, + query: JString<'local>, +) -> JString<'local> { + env.with_env(|env| -> JniResult> { + let prql_query = jstring_to_string(env, &query, "query")?; + let result = prql_to_pl(&prql_query).and_then(|x| pl_to_prql(&x)); + java_string_with_exception(result, env) + }) + .resolve::() } #[no_mangle] #[allow(non_snake_case)] -pub extern "system" fn Java_org_prql_prql4j_PrqlCompiler_toJson( - mut env: JNIEnv, - _class: JClass, - query: JString, -) -> jstring { - let prql_query = match jstring_to_string(&mut env, &query, "query") { - Some(s) => s, - None => return std::ptr::null_mut(), - }; - let result = prql_to_pl(&prql_query).and_then(|x| json::from_pl(&x)); - java_string_with_exception(result, &mut env) +pub extern "system" fn Java_org_prql_prql4j_PrqlCompiler_toJson<'local>( + mut env: EnvUnowned<'local>, + _class: JClass<'local>, + query: JString<'local>, +) -> JString<'local> { + env.with_env(|env| -> JniResult> { + let prql_query = jstring_to_string(env, &query, "query")?; + let result = prql_to_pl(&prql_query).and_then(|x| json::from_pl(&x)); + java_string_with_exception(result, env) + }) + .resolve::() } -fn jstring_to_string(env: &mut JNIEnv, s: &JString, name: &str) -> Option { - match env.get_string(s) { - Ok(js) => Some(js.into()), - Err(e) => { - throw_illegal_argument(env, &format!("failed to read {name}: {e}")); - None - } +fn jstring_to_string(env: &mut Env, s: &JString, name: &str) -> JniResult { + match s.try_to_string(env) { + Ok(text) => Ok(text), + Err(e) => Err(throw_illegal_argument( + env, + &format!("failed to read {name}: {e}"), + )), } } -fn throw_illegal_argument(env: &mut JNIEnv, message: &str) { - if let Err(e) = env.throw_new("java/lang/IllegalArgumentException", message) { - eprintln!("Error throwing IllegalArgumentException: {e:?}"); +/// Throws an `IllegalArgumentException` and returns the error to propagate out +/// of the `with_env` closure. The error policy leaves an already-pending +/// exception alone, so the exception thrown here is the one Java observes. +fn throw_illegal_argument(env: &mut Env, message: &str) -> JniError { + match env.throw_new( + jni_str!("java/lang/IllegalArgumentException"), + JNIString::from(message), + ) { + // `throw_new` reports `Err(Error::JavaException)` once the exception is + // pending; anything else means the throw itself failed. + Ok(()) => JniError::JavaException, + Err(e) => e, } } -fn java_string_with_exception(result: Result, env: &mut JNIEnv) -> jstring { +fn java_string_with_exception<'local>( + result: Result, + env: &mut Env<'local>, +) -> JniResult> { match result { Ok(text) => match env.new_string(text) { - Ok(js) => js.into_raw(), - Err(e) => { - throw_illegal_argument(env, &format!("failed to create java string: {e}")); - std::ptr::null_mut() - } + Ok(js) => Ok(js), + Err(e) => Err(throw_illegal_argument( + env, + &format!("failed to create java string: {e}"), + )), }, Err(err) => { let message = err.to_string(); - match env.find_class("java/lang/Exception") { - Ok(exception) => { - if let Err(e) = env.throw_new(exception, message) { - eprintln!("Error throwing exception: {e:?}"); - } - } - Err(e) => { - eprintln!("Error finding java/lang/Exception: {e:?}"); - } - } - std::ptr::null_mut() + let exception = env.find_class(jni_str!("java/lang/Exception"))?; + Err(match env.throw_new(exception, JNIString::from(message)) { + Ok(()) => JniError::JavaException, + Err(e) => e, + }) } } }