Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b64accb
build(native): add async-trait and futures deps for Java data sources
andygrove May 18, 2026
484cd12
refactor(native): lift jthrowable_to_string into shared jni_util module
andygrove May 18, 2026
b699291
feat(datasource): add DataSource interface and SessionContext.registe…
andygrove May 18, 2026
e16a99e
feat(native): add JavaDataSource TableProvider and JNI registration
andygrove May 18, 2026
79213dc
docs(native): clarify JavaScanExec safety + schema check + JVM attach
andygrove May 18, 2026
9c60f3c
feat(datasource)!: pass framework allocator to DataSource.scan
andygrove May 18, 2026
cd03d90
test(datasource): cover repeated scans within a single query
andygrove May 18, 2026
1004f6c
test(datasource): cover empty-stream scan
andygrove May 18, 2026
bf9c435
test(datasource): cover column projection through DataFusion
andygrove May 18, 2026
0ff2d8c
test(datasource): reject scan whose schema differs from registered sc…
andygrove May 18, 2026
248dc70
test(datasource): surface Java exception class and message from scan()
andygrove May 18, 2026
82d13fb
test(datasource): reject null ArrowReader from scan()
andygrove May 18, 2026
953fcf2
test(datasource): cover joining two registered Java data sources
andygrove May 18, 2026
af57098
docs(datasource): document SessionContext.registerDataSource
andygrove May 18, 2026
a4eb41e
docs(datasource): clarify scan() is per-physical-scan, not per-query
andygrove May 18, 2026
82c740a
feat(examples): add JDBC-backed DataSource example using H2 + arrow-jdbc
andygrove May 18, 2026
1df2bd2
refactor(datasource)!: rename DataSource API to TableProvider
andygrove May 19, 2026
9e8279d
Merge remote-tracking branch 'apache/main' into feat/columnar-value-udf
andygrove May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(native): lift jthrowable_to_string into shared jni_util module
  • Loading branch information
andygrove committed May 18, 2026
commit 484cd12314024110503b0d10c67be754cd8e5731
69 changes: 69 additions & 0 deletions native/src/jni_util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

//! Small shared helpers for JNI call sites.

use jni::objects::JThrowable;
use jni::JNIEnv;

/// Best-effort: extract class name and `getMessage()` from a Java throwable.
/// Anything that goes wrong collapses to a generic message so we don't
/// double-throw inside an error path.
///
/// `kind` and `name` are used to build the surfaced error message
/// (e.g., `kind="UDF" name="add_one"` -> `"Java UDF 'add_one' threw ..."`).
pub(crate) fn jthrowable_to_string(
env: &mut JNIEnv,
throwable: &JThrowable,
kind: &str,
name: &str,
) -> String {
let class_name_result = (|| -> jni::errors::Result<String> {
let class = env.call_method(throwable, "getClass", "()Ljava/lang/Class;", &[])?;
let class_obj = class.l()?;
let n = env.call_method(&class_obj, "getName", "()Ljava/lang/String;", &[])?;
let n_obj = n.l()?;
let n_str: String = env.get_string(&n_obj.into())?.into();
Ok(n_str)
})();
let class_name = match class_name_result {
Ok(s) => s,
Err(_) => {
env.exception_clear().ok();
"<unknown exception class>".to_string()
}
};

let message_result = (|| -> jni::errors::Result<String> {
let msg = env.call_method(throwable, "getMessage", "()Ljava/lang/String;", &[])?;
let msg_obj = msg.l()?;
if msg_obj.is_null() {
return Ok("<no message>".to_string());
}
let s: String = env.get_string(&msg_obj.into())?.into();
Ok(s)
})();
let message = match message_result {
Ok(s) => s,
Err(_) => {
env.exception_clear().ok();
"<no message>".to_string()
}
};

format!("Java {} '{}' threw {}: {}", kind, name, class_name, message)
}
1 change: 1 addition & 0 deletions native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
mod arrow;
mod csv;
mod errors;
mod jni_util;
mod json;
mod proto;
mod schema;
Expand Down
48 changes: 3 additions & 45 deletions native/src/udf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@ use datafusion::error::DataFusionError;
use datafusion::logical_expr::{
ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl, Signature, TypeSignature, Volatility,
};
use jni::objects::{GlobalRef, JStaticMethodID, JThrowable};
use jni::objects::{GlobalRef, JStaticMethodID};
use jni::signature::{Primitive, ReturnType};
use jni::sys::{jbyte, jlong, jvalue};
use jni::JNIEnv;

pub(crate) struct JavaScalarUdf {
pub(crate) name: String,
Expand Down Expand Up @@ -230,7 +229,8 @@ impl ScalarUDFImpl for JavaScalarUdf {
DataFusionError::Execution(format!("exception_occurred failed: {}", e))
})?;
env.exception_clear().ok();
let message = jthrowable_to_string(&mut env, &throwable, &self.name);
let message =
crate::jni_util::jthrowable_to_string(&mut env, &throwable, "UDF", &self.name);
return Err(DataFusionError::Execution(message));
}

Expand Down Expand Up @@ -292,45 +292,3 @@ pub(crate) fn volatility_from_byte(byte: u8) -> datafusion::error::Result<Volati
))),
}
}

/// Best-effort: extract class name and getMessage() from a Java throwable.
/// Anything that goes wrong collapses to a generic message so we don't
/// double-throw inside an error path.
fn jthrowable_to_string(env: &mut JNIEnv, throwable: &JThrowable, udf_name: &str) -> String {
let class_name_result = (|| -> jni::errors::Result<String> {
let class = env.call_method(throwable, "getClass", "()Ljava/lang/Class;", &[])?;
let class_obj = class.l()?;
let name = env.call_method(&class_obj, "getName", "()Ljava/lang/String;", &[])?;
let name_obj = name.l()?;
let name_str: String = env.get_string(&name_obj.into())?.into();
Ok(name_str)
})();
let class_name = match class_name_result {
Ok(s) => s,
Err(_) => {
// A reflective call itself threw — clear that secondary exception so the
// thread is in a clean state when we return to the JVM.
env.exception_clear().ok();
"<unknown exception class>".to_string()
}
};

let message_result = (|| -> jni::errors::Result<String> {
let msg = env.call_method(throwable, "getMessage", "()Ljava/lang/String;", &[])?;
let msg_obj = msg.l()?;
if msg_obj.is_null() {
return Ok("<no message>".to_string());
}
let s: String = env.get_string(&msg_obj.into())?.into();
Ok(s)
})();
let message = match message_result {
Ok(s) => s,
Err(_) => {
env.exception_clear().ok();
"<no message>".to_string()
}
};

format!("Java UDF '{}' threw {}: {}", udf_name, class_name, message)
}