Skip to main content

risingwave_jni_core/
lib.rs

1// Copyright 2023 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![feature(error_generic_member_access)]
16#![feature(once_cell_try)]
17#![feature(type_alias_impl_trait)]
18#![feature(try_blocks)]
19#![feature(used_with_arg)]
20
21pub mod jvm_runtime;
22mod macros;
23mod opendal_schema_history;
24mod tracing_slf4j;
25
26use std::backtrace::Backtrace;
27use std::marker::PhantomData;
28use std::ops::{Deref, DerefMut};
29use std::slice::from_raw_parts;
30use std::sync::{LazyLock, OnceLock};
31
32use anyhow::anyhow;
33use bytes::Bytes;
34use cfg_or_panic::cfg_or_panic;
35use chrono::{DateTime, Datelike, Timelike};
36use futures::TryStreamExt;
37use futures::stream::BoxStream;
38use jni::JNIEnv;
39use jni::objects::{
40    AutoElements, GlobalRef, JByteArray, JClass, JMethodID, JObject, JStaticMethodID, JString,
41    JValueOwned, ReleaseMode,
42};
43use jni::signature::ReturnType;
44use jni::sys::{
45    JNI_FALSE, JNI_TRUE, jboolean, jbyte, jdouble, jfloat, jint, jlong, jshort, jsize, jvalue,
46};
47pub use paste::paste;
48use prost::{DecodeError, Message};
49use risingwave_common::array::{ArrayError, StreamChunk};
50use risingwave_common::catalog::cdc_type_compatibility::cdc_source_column_type_compatible;
51use risingwave_common::hash::VirtualNode;
52use risingwave_common::row::{OwnedRow, Row};
53use risingwave_common::test_prelude::StreamChunkTestExt;
54use risingwave_common::types::{Decimal, ScalarRefImpl};
55use risingwave_common::util::panic::rw_catch_unwind;
56use risingwave_pb::catalog::table::CdcTableType as PbCdcTableType;
57use risingwave_pb::connector_service::{
58    GetEventStreamResponse, SinkCoordinatorStreamRequest, SinkCoordinatorStreamResponse,
59    SinkWriterStreamRequest, SinkWriterStreamResponse,
60};
61use risingwave_pb::data::Op;
62use risingwave_pb::data::data_type::TypeName as PbTypeName;
63use thiserror::Error;
64use thiserror_ext::AsReport;
65use tokio::runtime::Runtime;
66use tokio::sync::mpsc::{Receiver, Sender};
67use tracing_slf4j::*;
68
69/// Enable JVM and Java libraries.
70///
71/// This macro forces this crate to be linked, which registers the JVM builder.
72#[macro_export]
73macro_rules! enable {
74    () => {
75        use risingwave_jni_core as _;
76    };
77}
78
79pub static JAVA_BINDING_ASYNC_RUNTIME: LazyLock<Runtime> =
80    LazyLock::new(|| tokio::runtime::Runtime::new().unwrap());
81
82#[derive(Error, Debug)]
83pub enum BindingError {
84    #[error("JniError {error}")]
85    Jni {
86        #[from]
87        error: jni::errors::Error,
88        backtrace: Backtrace,
89    },
90
91    #[error("StorageError {error}")]
92    Storage {
93        #[from]
94        error: anyhow::Error,
95        backtrace: Backtrace,
96    },
97
98    #[error("DecodeError {error}")]
99    Decode {
100        #[from]
101        error: DecodeError,
102        backtrace: Backtrace,
103    },
104
105    #[error("StreamChunkArrayError {error}")]
106    StreamChunkArray {
107        #[from]
108        error: ArrayError,
109        backtrace: Backtrace,
110    },
111}
112
113type Result<T> = std::result::Result<T, BindingError>;
114
115pub fn to_guarded_slice<'array, 'env>(
116    array: &'array JByteArray<'env>,
117    env: &'array mut JNIEnv<'env>,
118) -> Result<SliceGuard<'env, 'array>> {
119    unsafe {
120        let array = env.get_array_elements(array, ReleaseMode::NoCopyBack)?;
121        let slice = from_raw_parts(array.as_ptr() as *mut u8, array.len());
122
123        Ok(SliceGuard {
124            _array: array,
125            slice,
126        })
127    }
128}
129
130/// Wrapper around `&[u8]` derived from `jbyteArray` to prevent it from being auto-released.
131pub struct SliceGuard<'env, 'array> {
132    _array: AutoElements<'env, 'env, 'array, jbyte>,
133    slice: &'array [u8],
134}
135
136impl Deref for SliceGuard<'_, '_> {
137    type Target = [u8];
138
139    fn deref(&self) -> &Self::Target {
140        self.slice
141    }
142}
143
144#[repr(transparent)]
145pub struct Pointer<'a, T> {
146    pointer: jlong,
147    _phantom: PhantomData<&'a T>,
148}
149
150impl<T> Default for Pointer<'_, T> {
151    fn default() -> Self {
152        Self {
153            pointer: 0,
154            _phantom: Default::default(),
155        }
156    }
157}
158
159impl<T> From<T> for Pointer<'static, T> {
160    fn from(value: T) -> Self {
161        Pointer {
162            pointer: Box::into_raw(Box::new(value)) as jlong,
163            _phantom: PhantomData,
164        }
165    }
166}
167
168impl<'a, T> Pointer<'a, T> {
169    fn as_ref(&self) -> &'a T {
170        assert!(self.pointer != 0);
171        unsafe { &*(self.pointer as *const T) }
172    }
173
174    fn as_mut(&mut self) -> &'a mut T {
175        assert!(self.pointer != 0);
176        unsafe { &mut *(self.pointer as *mut T) }
177    }
178}
179
180/// A pointer that owns the object it points to.
181///
182/// Note that dropping an `OwnedPointer` does not release the object.
183/// Instead, you should call [`OwnedPointer::release`] manually.
184pub type OwnedPointer<T> = Pointer<'static, T>;
185
186impl<T> OwnedPointer<T> {
187    /// Consume `self` and return the pointer value. Used for passing to JNI.
188    pub fn into_pointer(self) -> jlong {
189        self.pointer
190    }
191
192    /// Release the object behind the pointer.
193    fn release(self) {
194        tracing::debug!(
195            type_name = std::any::type_name::<T>(),
196            address = %format_args!("{:x}", self.pointer),
197            "release jni OwnedPointer"
198        );
199        assert!(self.pointer != 0);
200        unsafe { drop(Box::from_raw(self.pointer as *mut T)) }
201    }
202}
203
204/// In most Jni interfaces, the first parameter is `JNIEnv`, and the second parameter is `JClass`.
205/// This struct simply encapsulates the two common parameters into a single struct for simplicity.
206#[repr(C)]
207pub struct EnvParam<'a> {
208    env: JNIEnv<'a>,
209    class: JClass<'a>,
210}
211
212impl<'a> Deref for EnvParam<'a> {
213    type Target = JNIEnv<'a>;
214
215    fn deref(&self) -> &Self::Target {
216        &self.env
217    }
218}
219
220impl DerefMut for EnvParam<'_> {
221    fn deref_mut(&mut self) -> &mut Self::Target {
222        &mut self.env
223    }
224}
225
226impl<'a> EnvParam<'a> {
227    pub fn get_class(&self) -> &JClass<'a> {
228        &self.class
229    }
230}
231
232pub fn execute_and_catch<'env, F, Ret>(mut env: EnvParam<'env>, inner: F) -> Ret
233where
234    F: FnOnce(&mut EnvParam<'env>) -> Result<Ret>,
235    Ret: Default + 'env,
236{
237    match rw_catch_unwind(std::panic::AssertUnwindSafe(|| inner(&mut env))) {
238        Ok(Ok(ret)) => ret,
239        Ok(Err(e)) => {
240            match e {
241                BindingError::Jni {
242                    error: jni::errors::Error::JavaException,
243                    backtrace,
244                } => {
245                    tracing::error!("get JavaException thrown from: {:?}", backtrace);
246                    // the exception is already thrown. No need to throw again
247                }
248                _ => {
249                    env.throw(format!("get error while processing: {:?}", e.as_report()))
250                        .expect("should be able to throw");
251                }
252            }
253            Ret::default()
254        }
255        Err(e) => {
256            env.throw(format!("panic while processing: {:?}", e))
257                .expect("should be able to throw");
258            Ret::default()
259        }
260    }
261}
262
263#[derive(Default)]
264struct JavaClassMethodCache {
265    big_decimal_ctor: OnceLock<(GlobalRef, JMethodID)>,
266
267    timestamp_ctor: OnceLock<(GlobalRef, JStaticMethodID)>,
268    timestamptz_ctor: OnceLock<(GlobalRef, JStaticMethodID)>,
269    date_ctor: OnceLock<(GlobalRef, JStaticMethodID)>,
270    time_ctor: OnceLock<(GlobalRef, JStaticMethodID)>,
271    instant_ctor: OnceLock<(GlobalRef, JStaticMethodID)>,
272    utc: OnceLock<GlobalRef>,
273}
274
275mod opaque_type {
276    use super::*;
277    // TODO: may only return a RowRef
278    pub type StreamChunkRowIterator<'a> = impl Iterator<Item = (Op, OwnedRow)> + 'a;
279
280    impl<'a> JavaBindingIteratorInner<'a> {
281        #[define_opaque(StreamChunkRowIterator)]
282        pub(super) fn from_chunk(chunk: &'a StreamChunk) -> JavaBindingIteratorInner<'a> {
283            JavaBindingIteratorInner::StreamChunk(
284                chunk
285                    .rows()
286                    .map(|(op, row)| (op.to_protobuf(), row.to_owned_row())),
287            )
288        }
289    }
290}
291pub use opaque_type::StreamChunkRowIterator;
292pub type HummockJavaBindingIterator = BoxStream<'static, anyhow::Result<(Bytes, OwnedRow)>>;
293pub enum JavaBindingIteratorInner<'a> {
294    Hummock(HummockJavaBindingIterator),
295    StreamChunk(StreamChunkRowIterator<'a>),
296}
297
298enum RowExtra {
299    Op(Op),
300    Key(Bytes),
301}
302
303impl RowExtra {
304    fn as_op(&self) -> Op {
305        match self {
306            RowExtra::Op(op) => *op,
307            RowExtra::Key(_) => unreachable!("should be op"),
308        }
309    }
310
311    fn as_key(&self) -> &Bytes {
312        match self {
313            RowExtra::Key(key) => key,
314            RowExtra::Op(_) => unreachable!("should be key"),
315        }
316    }
317}
318
319struct RowCursor {
320    row: OwnedRow,
321    extra: RowExtra,
322}
323
324pub struct JavaBindingIterator<'a> {
325    inner: JavaBindingIteratorInner<'a>,
326    cursor: Option<RowCursor>,
327    class_cache: JavaClassMethodCache,
328}
329
330impl JavaBindingIterator<'static> {
331    pub fn new_hummock_iter(iter: HummockJavaBindingIterator) -> Self {
332        Self {
333            inner: JavaBindingIteratorInner::Hummock(iter),
334            cursor: None,
335            class_cache: Default::default(),
336        }
337    }
338}
339
340impl Deref for JavaBindingIterator<'_> {
341    type Target = OwnedRow;
342
343    fn deref(&self) -> &Self::Target {
344        &self
345            .cursor
346            .as_ref()
347            .expect("should exist when call row methods")
348            .row
349    }
350}
351
352#[unsafe(no_mangle)]
353extern "system" fn Java_com_risingwave_java_binding_Binding_defaultVnodeCount(
354    _env: EnvParam<'_>,
355) -> jint {
356    VirtualNode::COUNT_FOR_COMPAT as jint
357}
358
359fn jstring_to_option(env: &mut EnvParam<'_>, value: JString<'_>) -> Result<Option<String>> {
360    if value.is_null() {
361        return Ok(None);
362    }
363
364    Ok(Some(env.get_string(&value)?.into()))
365}
366
367#[unsafe(no_mangle)]
368extern "system" fn Java_com_risingwave_java_binding_Binding_validateCdcSourceColumnType<'a>(
369    env: EnvParam<'a>,
370    cdc_table_type: jint,
371    upstream_type_name: JString<'a>,
372    rw_type_name: jint,
373    char_max_length: jlong,
374    is_unsigned: jboolean,
375    postgres_udt_name: JString<'a>,
376) -> jboolean {
377    execute_and_catch(env, move |env| {
378        let cdc_table_type =
379            PbCdcTableType::try_from(cdc_table_type).unwrap_or(PbCdcTableType::Unspecified);
380        let rw_type_name =
381            PbTypeName::try_from(rw_type_name).unwrap_or(PbTypeName::TypeUnspecified);
382        let upstream_type_name = jstring_to_option(env, upstream_type_name)?.unwrap_or_default();
383        let postgres_udt_name = jstring_to_option(env, postgres_udt_name)?;
384        let char_max_length = (char_max_length >= 0).then_some(char_max_length);
385
386        Ok(
387            if cdc_source_column_type_compatible(
388                cdc_table_type,
389                &upstream_type_name,
390                rw_type_name,
391                char_max_length,
392                is_unsigned != JNI_FALSE,
393                postgres_udt_name.as_deref(),
394            ) {
395                JNI_TRUE
396            } else {
397                JNI_FALSE
398            },
399        )
400    })
401}
402
403#[cfg_or_panic(not(madsim))]
404#[unsafe(no_mangle)]
405extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorNewStreamChunk<'a>(
406    env: EnvParam<'a>,
407    chunk: Pointer<'a, StreamChunk>,
408) -> Pointer<'static, JavaBindingIterator<'a>> {
409    execute_and_catch(env, move |_env| {
410        let iter = JavaBindingIterator {
411            inner: JavaBindingIteratorInner::from_chunk(chunk.as_ref()),
412            cursor: None,
413            class_cache: Default::default(),
414        };
415        Ok(iter.into())
416    })
417}
418
419#[cfg_or_panic(not(madsim))]
420#[unsafe(no_mangle)]
421extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorNext<'a>(
422    env: EnvParam<'a>,
423    mut pointer: Pointer<'a, JavaBindingIterator<'a>>,
424) -> jboolean {
425    execute_and_catch(env, move |_env| {
426        let iter = pointer.as_mut();
427        match &mut iter.inner {
428            JavaBindingIteratorInner::Hummock(hummock_iter) => {
429                match JAVA_BINDING_ASYNC_RUNTIME.block_on(hummock_iter.try_next())? {
430                    None => {
431                        iter.cursor = None;
432                        Ok(JNI_FALSE)
433                    }
434                    Some((key, row)) => {
435                        iter.cursor = Some(RowCursor {
436                            row,
437                            extra: RowExtra::Key(key),
438                        });
439                        Ok(JNI_TRUE)
440                    }
441                }
442            }
443            JavaBindingIteratorInner::StreamChunk(stream_chunk_iter) => {
444                match stream_chunk_iter.next() {
445                    None => {
446                        iter.cursor = None;
447                        Ok(JNI_FALSE)
448                    }
449                    Some((op, row)) => {
450                        iter.cursor = Some(RowCursor {
451                            row,
452                            extra: RowExtra::Op(op),
453                        });
454                        Ok(JNI_TRUE)
455                    }
456                }
457            }
458        }
459    })
460}
461
462#[unsafe(no_mangle)]
463extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorClose<'a>(
464    _env: EnvParam<'a>,
465    pointer: OwnedPointer<JavaBindingIterator<'a>>,
466) {
467    pointer.release()
468}
469
470#[unsafe(no_mangle)]
471extern "system" fn Java_com_risingwave_java_binding_Binding_newStreamChunkFromPayload<'a>(
472    env: EnvParam<'a>,
473    stream_chunk_payload: JByteArray<'a>,
474) -> Pointer<'static, StreamChunk> {
475    execute_and_catch(env, move |env| {
476        let prost_stream_chumk =
477            Message::decode(to_guarded_slice(&stream_chunk_payload, env)?.deref())?;
478        Ok(StreamChunk::from_protobuf(&prost_stream_chumk)?.into())
479    })
480}
481
482#[unsafe(no_mangle)]
483extern "system" fn Java_com_risingwave_java_binding_Binding_newStreamChunkFromPretty<'a>(
484    env: EnvParam<'a>,
485    str: JString<'a>,
486) -> Pointer<'static, StreamChunk> {
487    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
488        Ok(StreamChunk::from_pretty(env.get_string(&str)?.to_str().unwrap()).into())
489    })
490}
491
492#[unsafe(no_mangle)]
493extern "system" fn Java_com_risingwave_java_binding_Binding_streamChunkClose(
494    _env: EnvParam<'_>,
495    chunk: OwnedPointer<StreamChunk>,
496) {
497    chunk.release()
498}
499
500#[unsafe(no_mangle)]
501extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetKey<'a>(
502    env: EnvParam<'a>,
503    pointer: Pointer<'a, JavaBindingIterator<'a>>,
504) -> JByteArray<'a> {
505    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
506        Ok(env.byte_array_from_slice(
507            pointer
508                .as_ref()
509                .cursor
510                .as_ref()
511                .expect("should exists when call get key")
512                .extra
513                .as_key()
514                .as_ref(),
515        )?)
516    })
517}
518
519#[unsafe(no_mangle)]
520extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetOp<'a>(
521    env: EnvParam<'a>,
522    pointer: Pointer<'a, JavaBindingIterator<'a>>,
523) -> jint {
524    execute_and_catch(env, move |_env| {
525        Ok(pointer
526            .as_ref()
527            .cursor
528            .as_ref()
529            .expect("should exist when call get op")
530            .extra
531            .as_op() as jint)
532    })
533}
534
535#[unsafe(no_mangle)]
536extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorIsNull<'a>(
537    env: EnvParam<'a>,
538    pointer: Pointer<'a, JavaBindingIterator<'a>>,
539    idx: jint,
540) -> jboolean {
541    execute_and_catch(env, move |_env| {
542        Ok(pointer.as_ref().datum_at(idx as usize).is_none() as jboolean)
543    })
544}
545
546#[unsafe(no_mangle)]
547extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetInt16Value<'a>(
548    env: EnvParam<'a>,
549    pointer: Pointer<'a, JavaBindingIterator<'a>>,
550    idx: jint,
551) -> jshort {
552    execute_and_catch(env, move |_env| {
553        Ok(pointer
554            .as_ref()
555            .datum_at(idx as usize)
556            .unwrap()
557            .into_int16())
558    })
559}
560
561#[unsafe(no_mangle)]
562extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetInt32Value<'a>(
563    env: EnvParam<'a>,
564    pointer: Pointer<'a, JavaBindingIterator<'a>>,
565    idx: jint,
566) -> jint {
567    execute_and_catch(env, move |_env| {
568        Ok(pointer
569            .as_ref()
570            .datum_at(idx as usize)
571            .unwrap()
572            .into_int32())
573    })
574}
575
576#[unsafe(no_mangle)]
577extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetInt64Value<'a>(
578    env: EnvParam<'a>,
579    pointer: Pointer<'a, JavaBindingIterator<'a>>,
580    idx: jint,
581) -> jlong {
582    execute_and_catch(env, move |_env| {
583        Ok(pointer
584            .as_ref()
585            .datum_at(idx as usize)
586            .unwrap()
587            .into_int64())
588    })
589}
590
591#[unsafe(no_mangle)]
592extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetFloatValue<'a>(
593    env: EnvParam<'a>,
594    pointer: Pointer<'a, JavaBindingIterator<'a>>,
595    idx: jint,
596) -> jfloat {
597    execute_and_catch(env, move |_env| {
598        Ok(pointer
599            .as_ref()
600            .datum_at(idx as usize)
601            .unwrap()
602            .into_float32()
603            .into())
604    })
605}
606
607#[unsafe(no_mangle)]
608extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetDoubleValue<'a>(
609    env: EnvParam<'a>,
610    pointer: Pointer<'a, JavaBindingIterator<'a>>,
611    idx: jint,
612) -> jdouble {
613    execute_and_catch(env, move |_env| {
614        Ok(pointer
615            .as_ref()
616            .datum_at(idx as usize)
617            .unwrap()
618            .into_float64()
619            .into())
620    })
621}
622
623#[unsafe(no_mangle)]
624extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetBooleanValue<'a>(
625    env: EnvParam<'a>,
626    pointer: Pointer<'a, JavaBindingIterator<'a>>,
627    idx: jint,
628) -> jboolean {
629    execute_and_catch(env, move |_env| {
630        Ok(pointer.as_ref().datum_at(idx as usize).unwrap().into_bool() as jboolean)
631    })
632}
633
634#[unsafe(no_mangle)]
635extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetStringValue<'a>(
636    env: EnvParam<'a>,
637    pointer: Pointer<'a, JavaBindingIterator<'a>>,
638    idx: jint,
639) -> JString<'a> {
640    execute_and_catch(env, move |env: &mut EnvParam<'a>| {
641        Ok(env.new_string(pointer.as_ref().datum_at(idx as usize).unwrap().into_utf8())?)
642    })
643}
644
645#[unsafe(no_mangle)]
646extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetIntervalValue<'a>(
647    env: EnvParam<'a>,
648    pointer: Pointer<'a, JavaBindingIterator<'a>>,
649    idx: jint,
650) -> JString<'a> {
651    execute_and_catch(env, move |env: &mut EnvParam<'a>| {
652        let interval = pointer
653            .as_ref()
654            .datum_at(idx as usize)
655            .unwrap()
656            .into_interval()
657            .as_iso_8601();
658        Ok(env.new_string(interval)?)
659    })
660}
661
662#[unsafe(no_mangle)]
663extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetJsonbValue<'a>(
664    env: EnvParam<'a>,
665    pointer: Pointer<'a, JavaBindingIterator<'a>>,
666    idx: jint,
667) -> JString<'a> {
668    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
669        let jsonb = pointer
670            .as_ref()
671            .datum_at(idx as usize)
672            .unwrap()
673            .into_jsonb()
674            .to_string();
675        Ok(env.new_string(jsonb)?)
676    })
677}
678
679#[unsafe(no_mangle)]
680extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetTimestampValue<'a>(
681    env: EnvParam<'a>,
682    pointer: Pointer<'a, JavaBindingIterator<'a>>,
683    idx: jint,
684) -> JObject<'a> {
685    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
686        let value = pointer
687            .as_ref()
688            .datum_at(idx as usize)
689            .unwrap()
690            .into_timestamp();
691
692        let sig = gen_jni_sig!(java.time.LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond));
693
694        let (timestamp_class_ref, constructor) = pointer
695            .as_ref()
696            .class_cache
697            .timestamp_ctor
698            .get_or_try_init(|| {
699                let cls = env.find_class(gen_class_name!(java.time.LocalDateTime))?;
700                let init_method = env.get_static_method_id(&cls, "of", sig)?;
701                Ok::<_, jni::errors::Error>((env.new_global_ref(cls)?, init_method))
702            })?;
703        unsafe {
704            let JValueOwned::Object(timestamp_obj) = env.call_static_method_unchecked(
705                <&JClass<'_>>::from(timestamp_class_ref.as_obj()),
706                *constructor,
707                ReturnType::Object,
708                &[
709                    jvalue { i: value.0.year() },
710                    jvalue {
711                        i: value.0.month() as i32,
712                    },
713                    jvalue {
714                        i: value.0.day() as i32,
715                    },
716                    jvalue {
717                        i: value.0.hour() as i32,
718                    },
719                    jvalue {
720                        i: value.0.minute() as i32,
721                    },
722                    jvalue {
723                        i: value.0.second() as i32,
724                    },
725                    jvalue {
726                        i: value.0.nanosecond() as i32,
727                    },
728                ],
729            )?
730            else {
731                return Err(BindingError::from(jni::errors::Error::MethodNotFound {
732                    name: "of".to_owned(),
733                    sig: sig.into(),
734                }));
735            };
736            Ok(timestamp_obj)
737        }
738    })
739}
740
741#[unsafe(no_mangle)]
742extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetTimestamptzValue<'a>(
743    env: EnvParam<'a>,
744    pointer: Pointer<'a, JavaBindingIterator<'a>>,
745    idx: jint,
746) -> JObject<'a> {
747    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
748        let value = pointer
749            .as_ref()
750            .datum_at(idx as usize)
751            .unwrap()
752            .into_timestamptz();
753
754        let instant_sig =
755            gen_jni_sig!(java.time.Instant ofEpochSecond(long epochSecond, long nanoAdjustment));
756
757        let (instant_class_ref, instant_constructor) = pointer
758            .as_ref()
759            .class_cache
760            .instant_ctor
761            .get_or_try_init(|| {
762                let cls = env.find_class(gen_class_name!(java.time.Instant))?;
763                let init_method = env.get_static_method_id(&cls, "ofEpochSecond", instant_sig)?;
764                Ok::<_, jni::errors::Error>((env.new_global_ref(cls)?, init_method))
765            })?;
766        let instant_obj = unsafe {
767            let JValueOwned::Object(instant_obj) = env.call_static_method_unchecked(
768                <&JClass<'_>>::from(instant_class_ref.as_obj()),
769                *instant_constructor,
770                ReturnType::Object,
771                &[
772                    jvalue {
773                        j: value.timestamp(),
774                    },
775                    jvalue {
776                        j: value.timestamp_subsec_nanos() as i64,
777                    },
778                ],
779            )?
780            else {
781                return Err(BindingError::from(jni::errors::Error::MethodNotFound {
782                    name: "ofEpochSecond".to_owned(),
783                    sig: instant_sig.into(),
784                }));
785            };
786            instant_obj
787        };
788
789        let utc_ref = pointer.as_ref().class_cache.utc.get_or_try_init(|| {
790            let cls = env.find_class(gen_class_name!(java.time.ZoneOffset))?;
791            let utc = env
792                .get_static_field(&cls, "UTC", gen_jni_type_sig!(java.time.ZoneOffset))?
793                .l()?;
794            env.new_global_ref(utc)
795        })?;
796
797        let sig = gen_jni_sig!(java.time.OffsetDateTime ofInstant(java.time.Instant instant, java.time.ZoneId zone));
798
799        let (timestamptz_class_ref, constructor) = pointer
800            .as_ref()
801            .class_cache
802            .timestamptz_ctor
803            .get_or_try_init(|| {
804                let cls = env.find_class(gen_class_name!(java.time.OffsetDateTime))?;
805                let init_method = env.get_static_method_id(&cls, "ofInstant", sig)?;
806                Ok::<_, jni::errors::Error>((env.new_global_ref(cls)?, init_method))
807            })?;
808        unsafe {
809            let JValueOwned::Object(timestamptz_obj) = env.call_static_method_unchecked(
810                <&JClass<'_>>::from(timestamptz_class_ref.as_obj()),
811                *constructor,
812                ReturnType::Object,
813                &[
814                    jvalue {
815                        l: instant_obj.as_raw(),
816                    },
817                    jvalue {
818                        l: utc_ref.as_obj().as_raw(),
819                    },
820                ],
821            )?
822            else {
823                return Err(BindingError::from(jni::errors::Error::MethodNotFound {
824                    name: "ofInstant".to_owned(),
825                    sig: sig.into(),
826                }));
827            };
828            Ok(timestamptz_obj)
829        }
830    })
831}
832
833#[unsafe(no_mangle)]
834extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetDecimalValue<'a>(
835    env: EnvParam<'a>,
836    pointer: Pointer<'a, JavaBindingIterator<'a>>,
837    idx: jint,
838) -> JObject<'a> {
839    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
840        let decimal_value = pointer
841            .as_ref()
842            .datum_at(idx as usize)
843            .unwrap()
844            .into_decimal();
845
846        match decimal_value {
847            Decimal::NaN | Decimal::NegativeInf | Decimal::PositiveInf => {
848                return Ok(JObject::null());
849            }
850            Decimal::Normalized(_) => {}
851        };
852
853        let value = decimal_value.to_string();
854        let string_value = env.new_string(value)?;
855        let (decimal_class_ref, constructor) = pointer
856            .as_ref()
857            .class_cache
858            .big_decimal_ctor
859            .get_or_try_init(|| {
860                let cls = env.find_class("java/math/BigDecimal")?;
861                let init_method = env.get_method_id(&cls, "<init>", "(Ljava/lang/String;)V")?;
862                Ok::<_, jni::errors::Error>((env.new_global_ref(cls)?, init_method))
863            })?;
864        unsafe {
865            let decimal_class = <&JClass<'_>>::from(decimal_class_ref.as_obj());
866            let date_obj = env.new_object_unchecked(
867                decimal_class,
868                *constructor,
869                &[jvalue {
870                    l: string_value.into_raw(),
871                }],
872            )?;
873            Ok(date_obj)
874        }
875    })
876}
877
878#[unsafe(no_mangle)]
879extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetDateValue<'a>(
880    env: EnvParam<'a>,
881    pointer: Pointer<'a, JavaBindingIterator<'a>>,
882    idx: jint,
883) -> JObject<'a> {
884    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
885        let value = pointer.as_ref().datum_at(idx as usize).unwrap().into_date();
886        let epoch_days = (value.0 - DateTime::UNIX_EPOCH.date_naive()).num_days();
887
888        let sig = gen_jni_sig!(java.time.LocalDate ofEpochDay(long));
889
890        let (date_class_ref, constructor) =
891            pointer.as_ref().class_cache.date_ctor.get_or_try_init(|| {
892                let cls = env.find_class(gen_class_name!(java.time.LocalDate))?;
893                let init_method = env.get_static_method_id(&cls, "ofEpochDay", sig)?;
894                Ok::<_, jni::errors::Error>((env.new_global_ref(cls)?, init_method))
895            })?;
896        unsafe {
897            let JValueOwned::Object(date_obj) = env.call_static_method_unchecked(
898                <&JClass<'_>>::from(date_class_ref.as_obj()),
899                *constructor,
900                ReturnType::Object,
901                &[jvalue { j: epoch_days }],
902            )?
903            else {
904                return Err(BindingError::from(jni::errors::Error::MethodNotFound {
905                    name: "ofEpochDay".to_owned(),
906                    sig: sig.into(),
907                }));
908            };
909            Ok(date_obj)
910        }
911    })
912}
913
914#[unsafe(no_mangle)]
915extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetTimeValue<'a>(
916    env: EnvParam<'a>,
917    pointer: Pointer<'a, JavaBindingIterator<'a>>,
918    idx: jint,
919) -> JObject<'a> {
920    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
921        let value = pointer.as_ref().datum_at(idx as usize).unwrap().into_time();
922
923        let sig = gen_jni_sig!(java.time.LocalTime of(int hour, int minute, int second, int nanoOfSecond));
924
925        let (time_class_ref, constructor) =
926            pointer.as_ref().class_cache.time_ctor.get_or_try_init(|| {
927                let cls = env.find_class(gen_class_name!(java.time.LocalTime))?;
928                let init_method = env.get_static_method_id(&cls, "of", sig)?;
929                Ok::<_, jni::errors::Error>((env.new_global_ref(cls)?, init_method))
930            })?;
931        unsafe {
932            let JValueOwned::Object(time_obj) = env.call_static_method_unchecked(
933                <&JClass<'_>>::from(time_class_ref.as_obj()),
934                *constructor,
935                ReturnType::Object,
936                &[
937                    jvalue {
938                        i: value.0.hour() as i32,
939                    },
940                    jvalue {
941                        i: value.0.minute() as i32,
942                    },
943                    jvalue {
944                        i: value.0.second() as i32,
945                    },
946                    jvalue {
947                        i: value.0.nanosecond() as i32,
948                    },
949                ],
950            )?
951            else {
952                return Err(BindingError::from(jni::errors::Error::MethodNotFound {
953                    name: "of".to_owned(),
954                    sig: sig.into(),
955                }));
956            };
957            Ok(time_obj)
958        }
959    })
960}
961
962#[unsafe(no_mangle)]
963extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetByteaValue<'a>(
964    env: EnvParam<'a>,
965    pointer: Pointer<'a, JavaBindingIterator<'a>>,
966    idx: jint,
967) -> JByteArray<'a> {
968    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
969        let bytes = pointer
970            .as_ref()
971            .datum_at(idx as usize)
972            .unwrap()
973            .into_bytea();
974        Ok(env.byte_array_from_slice(bytes)?)
975    })
976}
977
978#[unsafe(no_mangle)]
979extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorGetArrayValue<'a>(
980    env: EnvParam<'a>,
981    pointer: Pointer<'a, JavaBindingIterator<'a>>,
982    idx: jint,
983    class: JClass<'a>,
984) -> JObject<'a> {
985    execute_and_catch(env, move |env: &mut EnvParam<'_>| {
986        let elems = pointer
987            .as_ref()
988            .datum_at(idx as usize)
989            .unwrap()
990            .into_list()
991            .iter();
992
993        // convert the Rust elements to a Java object array (Object[])
994        let jarray = env.new_object_array(elems.len() as jsize, &class, JObject::null())?;
995
996        for (i, ele) in elems.enumerate() {
997            let index = i as jsize;
998            match ele {
999                None => env.set_object_array_element(&jarray, i as jsize, JObject::null())?,
1000                Some(val) => match val {
1001                    ScalarRefImpl::Int16(v) => {
1002                        let o = call_static_method!(
1003                            env,
1004                            {Short},
1005                            {Short	valueOf(short s)},
1006                            v
1007                        )?;
1008                        env.set_object_array_element(&jarray, index, &o)?;
1009                    }
1010                    ScalarRefImpl::Int32(v) => {
1011                        let o = call_static_method!(
1012                            env,
1013                            {Integer},
1014                            {Integer	valueOf(int i)},
1015                            v
1016                        )?;
1017                        env.set_object_array_element(&jarray, index, &o)?;
1018                    }
1019                    ScalarRefImpl::Int64(v) => {
1020                        let o = call_static_method!(
1021                            env,
1022                            {Long},
1023                            {Long	valueOf(long l)},
1024                            v
1025                        )?;
1026                        env.set_object_array_element(&jarray, index, &o)?;
1027                    }
1028                    ScalarRefImpl::Float32(v) => {
1029                        let o = call_static_method!(
1030                            env,
1031                            {Float},
1032                            {Float	valueOf(float f)},
1033                            v.into_inner()
1034                        )?;
1035                        env.set_object_array_element(&jarray, index, &o)?;
1036                    }
1037                    ScalarRefImpl::Float64(v) => {
1038                        let o = call_static_method!(
1039                            env,
1040                            {Double},
1041                            {Double	valueOf(double d)},
1042                            v.into_inner()
1043                        )?;
1044                        env.set_object_array_element(&jarray, index, &o)?;
1045                    }
1046                    ScalarRefImpl::Utf8(v) => {
1047                        let obj = env.new_string(v)?;
1048                        env.set_object_array_element(&jarray, index, obj)?
1049                    }
1050                    _ => env.set_object_array_element(&jarray, index, JObject::null())?,
1051                },
1052            }
1053        }
1054        let output = unsafe { JObject::from_raw(jarray.into_raw()) };
1055        Ok(output)
1056    })
1057}
1058
1059pub type JniSenderType<T> = Sender<anyhow::Result<T>>;
1060pub type JniReceiverType<T> = Receiver<T>;
1061
1062/// Send messages to the channel received by `CdcSplitReader`.
1063/// If msg is null, just check whether the channel is closed.
1064/// Return true if sending is successful, otherwise, return false so that caller can stop
1065/// gracefully.
1066#[unsafe(no_mangle)]
1067extern "system" fn Java_com_risingwave_java_binding_Binding_sendCdcSourceMsgToChannel<'a>(
1068    env: EnvParam<'a>,
1069    channel: Pointer<'a, JniSenderType<GetEventStreamResponse>>,
1070    msg: JByteArray<'a>,
1071) -> jboolean {
1072    execute_and_catch(env, move |env| {
1073        // If msg is null means just check whether channel is closed.
1074        if msg.is_null() {
1075            if channel.as_ref().is_closed() {
1076                return Ok(JNI_FALSE);
1077            } else {
1078                return Ok(JNI_TRUE);
1079            }
1080        }
1081
1082        let get_event_stream_response: GetEventStreamResponse =
1083            Message::decode(to_guarded_slice(&msg, env)?.deref())?;
1084
1085        match channel
1086            .as_ref()
1087            .blocking_send(Ok(get_event_stream_response))
1088        {
1089            Ok(_) => Ok(JNI_TRUE),
1090            Err(e) => {
1091                tracing::info!(error = %e.as_report(), "send error");
1092                Ok(JNI_FALSE)
1093            }
1094        }
1095    })
1096}
1097
1098#[unsafe(no_mangle)]
1099extern "system" fn Java_com_risingwave_java_binding_Binding_sendCdcSourceErrorToChannel<'a>(
1100    env: EnvParam<'a>,
1101    channel: Pointer<'a, JniSenderType<GetEventStreamResponse>>,
1102    msg: JString<'a>,
1103) -> jboolean {
1104    execute_and_catch(env, move |env| {
1105        let ret = env.get_string(&msg);
1106        match ret {
1107            Ok(str) => {
1108                let err_msg: String = str.into();
1109                match channel.as_ref().blocking_send(Err(anyhow!(err_msg))) {
1110                    Ok(_) => Ok(JNI_TRUE),
1111                    Err(e) => {
1112                        tracing::info!(error = ?e.as_report(), "send error");
1113                        Ok(JNI_FALSE)
1114                    }
1115                }
1116            }
1117            Err(err) => {
1118                if msg.is_null() {
1119                    tracing::warn!("source error message is null");
1120                    Ok(JNI_TRUE)
1121                } else {
1122                    tracing::error!(error = ?err.as_report(), "source error message should be a java string");
1123                    Ok(JNI_FALSE)
1124                }
1125            }
1126        }
1127    })
1128}
1129
1130#[unsafe(no_mangle)]
1131extern "system" fn Java_com_risingwave_java_binding_Binding_cdcSourceSenderClose(
1132    _env: EnvParam<'_>,
1133    channel: OwnedPointer<JniSenderType<GetEventStreamResponse>>,
1134) {
1135    channel.release();
1136}
1137
1138pub enum JniSinkWriterStreamRequest {
1139    PbRequest(SinkWriterStreamRequest),
1140    Chunk {
1141        epoch: u64,
1142        batch_id: u64,
1143        chunk: StreamChunk,
1144    },
1145}
1146
1147impl From<SinkWriterStreamRequest> for JniSinkWriterStreamRequest {
1148    fn from(value: SinkWriterStreamRequest) -> Self {
1149        Self::PbRequest(value)
1150    }
1151}
1152
1153#[unsafe(no_mangle)]
1154pub extern "system" fn Java_com_risingwave_java_binding_Binding_recvSinkWriterRequestFromChannel<
1155    'a,
1156>(
1157    env: EnvParam<'a>,
1158    mut channel: Pointer<'a, JniReceiverType<JniSinkWriterStreamRequest>>,
1159) -> JObject<'a> {
1160    execute_and_catch(env, move |env| match channel.as_mut().blocking_recv() {
1161        Some(msg) => {
1162            let obj = match msg {
1163                JniSinkWriterStreamRequest::PbRequest(request) => {
1164                    let bytes = env.byte_array_from_slice(&Message::encode_to_vec(&request))?;
1165                    let jobj = JObject::from(bytes);
1166                    call_static_method!(
1167                        env,
1168                        {com.risingwave.java.binding.JniSinkWriterStreamRequest},
1169                        {com.risingwave.java.binding.JniSinkWriterStreamRequest fromSerializedPayload(byte[] payload)},
1170                        &jobj
1171                    )?
1172                }
1173                JniSinkWriterStreamRequest::Chunk {
1174                    epoch,
1175                    batch_id,
1176                    chunk,
1177                } => {
1178                    let pointer = Box::into_raw(Box::new(chunk));
1179                    call_static_method!(
1180                        env,
1181                        {com.risingwave.java.binding.JniSinkWriterStreamRequest},
1182                        {com.risingwave.java.binding.JniSinkWriterStreamRequest fromStreamChunkOwnedPointer(long pointer, long epoch, long batchId)},
1183                        pointer as u64, epoch, batch_id
1184                    )
1185                    .inspect_err(|_| unsafe {
1186                        // release the stream chunk on err
1187                        drop(Box::from_raw(pointer));
1188                    })?
1189                }
1190            };
1191            Ok(obj)
1192        }
1193        None => Ok(JObject::null()),
1194    })
1195}
1196
1197#[unsafe(no_mangle)]
1198pub extern "system" fn Java_com_risingwave_java_binding_Binding_sendSinkWriterResponseToChannel<
1199    'a,
1200>(
1201    env: EnvParam<'a>,
1202    channel: Pointer<'a, JniSenderType<SinkWriterStreamResponse>>,
1203    msg: JByteArray<'a>,
1204) -> jboolean {
1205    execute_and_catch(env, move |env| {
1206        let sink_writer_stream_response: SinkWriterStreamResponse =
1207            Message::decode(to_guarded_slice(&msg, env)?.deref())?;
1208
1209        match channel
1210            .as_ref()
1211            .blocking_send(Ok(sink_writer_stream_response))
1212        {
1213            Ok(_) => Ok(JNI_TRUE),
1214            Err(e) => {
1215                tracing::info!(error = ?e.as_report(), "send error");
1216                Ok(JNI_FALSE)
1217            }
1218        }
1219    })
1220}
1221
1222#[unsafe(no_mangle)]
1223pub extern "system" fn Java_com_risingwave_java_binding_Binding_sendSinkWriterErrorToChannel<'a>(
1224    env: EnvParam<'a>,
1225    channel: Pointer<'a, Sender<anyhow::Result<SinkWriterStreamResponse>>>,
1226    msg: JString<'a>,
1227) -> jboolean {
1228    execute_and_catch(env, move |env| {
1229        let ret = env.get_string(&msg);
1230        match ret {
1231            Ok(str) => {
1232                let err_msg: String = str.into();
1233                match channel.as_ref().blocking_send(Err(anyhow!(err_msg))) {
1234                    Ok(_) => Ok(JNI_TRUE),
1235                    Err(e) => {
1236                        tracing::info!(error = ?e.as_report(), "send error");
1237                        Ok(JNI_FALSE)
1238                    }
1239                }
1240            }
1241            Err(err) => {
1242                if msg.is_null() {
1243                    tracing::warn!("sink error message is null");
1244                    Ok(JNI_TRUE)
1245                } else {
1246                    tracing::error!(error = ?err.as_report(), "sink error message should be a java string");
1247                    Ok(JNI_FALSE)
1248                }
1249            }
1250        }
1251    })
1252}
1253
1254#[unsafe(no_mangle)]
1255pub extern "system" fn Java_com_risingwave_java_binding_Binding_recvSinkCoordinatorRequestFromChannel<
1256    'a,
1257>(
1258    env: EnvParam<'a>,
1259    mut channel: Pointer<'a, JniReceiverType<SinkCoordinatorStreamRequest>>,
1260) -> JByteArray<'a> {
1261    execute_and_catch(env, move |env| match channel.as_mut().blocking_recv() {
1262        Some(msg) => {
1263            let bytes = env
1264                .byte_array_from_slice(&Message::encode_to_vec(&msg))
1265                .unwrap();
1266            Ok(bytes)
1267        }
1268        None => Ok(JObject::null().into()),
1269    })
1270}
1271
1272#[unsafe(no_mangle)]
1273pub extern "system" fn Java_com_risingwave_java_binding_Binding_sendSinkCoordinatorResponseToChannel<
1274    'a,
1275>(
1276    env: EnvParam<'a>,
1277    channel: Pointer<'a, JniSenderType<SinkCoordinatorStreamResponse>>,
1278    msg: JByteArray<'a>,
1279) -> jboolean {
1280    execute_and_catch(env, move |env| {
1281        let sink_coordinator_stream_response: SinkCoordinatorStreamResponse =
1282            Message::decode(to_guarded_slice(&msg, env)?.deref())?;
1283
1284        match channel
1285            .as_ref()
1286            .blocking_send(Ok(sink_coordinator_stream_response))
1287        {
1288            Ok(_) => Ok(JNI_TRUE),
1289            Err(e) => {
1290                tracing::info!(error = ?e.as_report(), "send error");
1291                Ok(JNI_FALSE)
1292            }
1293        }
1294    })
1295}
1296
1297#[cfg(test)]
1298mod tests {
1299    use risingwave_common::types::Timestamptz;
1300
1301    /// make sure that the [`ScalarRefImpl::Int64`] received by
1302    /// [`Java_com_risingwave_java_binding_Binding_iteratorGetTimestampValue`]
1303    /// is of type [`DataType::Timestamptz`] stored in microseconds
1304    #[test]
1305    fn test_timestamptz_to_i64() {
1306        assert_eq!(
1307            "2023-06-01 09:45:00+08:00".parse::<Timestamptz>().unwrap(),
1308            Timestamptz::from_micros(1_685_583_900_000_000).unwrap()
1309        );
1310    }
1311}