blob: f0b0364b0ba3091bb763ccffd61eafa258cc4e7b [file]
// 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.
use jni::objects::{GlobalRef, JByteArray, JObject, JObjectArray, JValue};
use jni::JavaVM;
use paimon_vindex_core::io::{ReadRequest, SeekRead};
use std::io;
use std::sync::Arc;
/// JNI-backed input stream that delegates to Java's VectorIndexInput.
pub struct JniSeekableStream {
jvm: Arc<JavaVM>,
stream_ref: Arc<GlobalRef>,
}
impl JniSeekableStream {
pub fn new(jvm: JavaVM, stream_ref: GlobalRef) -> Self {
JniSeekableStream {
jvm: Arc::new(jvm),
stream_ref: Arc::new(stream_ref),
}
}
}
impl SeekRead for JniSeekableStream {
/// Positional reads via VectorIndexInput.pread(long[] positions, byte[][] buffers).
fn pread(&mut self, ranges: &mut [ReadRequest<'_>]) -> io::Result<()> {
if ranges.is_empty() {
return Ok(());
}
let mut env = self
.jvm
.attach_current_thread()
.map_err(|e| io::Error::other(format!("JNI attach: {}", e)))?;
let positions = env
.new_long_array(ranges.len() as i32)
.map_err(|e| io::Error::other(format!("JNI alloc positions: {}", e)))?;
let position_values: Vec<i64> = ranges.iter().map(|range| range.pos as i64).collect();
env.set_long_array_region(&positions, 0, &position_values)
.map_err(|e| io::Error::other(format!("JNI set positions: {}", e)))?;
let byte_array_class = env
.find_class("[B")
.map_err(|e| io::Error::other(format!("JNI find byte[] class: {}", e)))?;
let buffers = env
.new_object_array(ranges.len() as i32, byte_array_class, JObject::null())
.map_err(|e| io::Error::other(format!("JNI alloc buffers: {}", e)))?;
for (idx, range) in ranges.iter().enumerate() {
let jbuf = env
.new_byte_array(range.buf.len() as i32)
.map_err(|e| io::Error::other(format!("JNI alloc range buffer: {}", e)))?;
env.set_object_array_element(&buffers, idx as i32, jbuf)
.map_err(|e| io::Error::other(format!("JNI set buffer: {}", e)))?;
}
env.call_method(
self.stream_ref.as_obj(),
"pread",
"([J[[B)V",
&[JValue::Object(&positions), JValue::Object(&buffers)],
)
.map_err(|e| io::Error::other(format!("JNI pread: {}", e)))?;
copy_java_buffers(&mut env, &buffers, ranges)
}
}
fn copy_java_buffers(
env: &mut jni::JNIEnv<'_>,
buffers: &JObjectArray<'_>,
ranges: &mut [ReadRequest<'_>],
) -> io::Result<()> {
for (idx, range) in ranges.iter_mut().enumerate() {
let obj = env
.get_object_array_element(buffers, idx as i32)
.map_err(|e| io::Error::other(format!("JNI get buffer: {}", e)))?;
let jbuf = JByteArray::from(obj);
let len = env
.get_array_length(&jbuf)
.map_err(|e| io::Error::other(format!("JNI get buffer length: {}", e)))?
as usize;
if len != range.buf.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Java pread returned buffer length {} != {}",
len,
range.buf.len()
),
));
}
if len > 0 {
let mut signed_buf = vec![0i8; range.buf.len()];
env.get_byte_array_region(&jbuf, 0, &mut signed_buf)
.map_err(|e| io::Error::other(format!("JNI get_region: {}", e)))?;
for (i, &b) in signed_buf.iter().enumerate() {
range.buf[i] = b as u8;
}
}
}
Ok(())
}
/// JNI-backed output stream that delegates to Java's PositionOutputStream.
pub struct JniOutputStream {
jvm: Arc<JavaVM>,
stream_ref: Arc<GlobalRef>,
pos: u64,
}
impl JniOutputStream {
pub fn new(jvm: JavaVM, stream_ref: GlobalRef) -> Self {
JniOutputStream {
jvm: Arc::new(jvm),
stream_ref: Arc::new(stream_ref),
pos: 0,
}
}
}
impl paimon_vindex_core::io::SeekWrite for JniOutputStream {
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
let mut env = self
.jvm
.attach_current_thread()
.map_err(|e| io::Error::other(format!("JNI attach: {}", e)))?;
let jbuf = env
.new_byte_array(buf.len() as i32)
.map_err(|e| io::Error::other(format!("JNI alloc: {}", e)))?;
let signed: Vec<i8> = buf.iter().map(|&b| b as i8).collect();
env.set_byte_array_region(&jbuf, 0, &signed)
.map_err(|e| io::Error::other(format!("JNI set_region: {}", e)))?;
env.call_method(
self.stream_ref.as_obj(),
"write",
"([B)V",
&[jni::objects::JValue::Object(&jbuf)],
)
.map_err(|e| io::Error::other(format!("JNI write: {}", e)))?;
self.pos += buf.len() as u64;
Ok(())
}
fn pos(&self) -> u64 {
self.pos
}
}