1#![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#[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
130pub 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
180pub type OwnedPointer<T> = Pointer<'static, T>;
185
186impl<T> OwnedPointer<T> {
187 pub fn into_pointer(self) -> jlong {
189 self.pointer
190 }
191
192 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#[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 }
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 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 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#[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() {
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 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 #[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}