@@ -913,6 +913,158 @@ JNIEXPORT void JNICALL Java_com_tidesdb_TidesDBIterator_nativeFree(JNIEnv *env,
913913 }
914914}
915915
916+ /**
917+ * Context stored as the commit hook ctx pointer.
918+ * Holds the JavaVM and a global reference to the Java CommitHook object.
919+ */
920+ typedef struct
921+ {
922+ JavaVM * jvm ;
923+ jobject hook_obj ; /* global reference to CommitHook */
924+ } java_hook_ctx_t ;
925+
926+ /**
927+ * C trampoline that bridges the tidesdb_commit_hook_fn callback to the Java CommitHook.onCommit
928+ * method. Fires synchronously on the committing thread (which is always a Java thread).
929+ */
930+ static int java_commit_hook_trampoline (const tidesdb_commit_op_t * ops , int num_ops ,
931+ uint64_t commit_seq , void * ctx )
932+ {
933+ java_hook_ctx_t * hctx = (java_hook_ctx_t * )ctx ;
934+ JNIEnv * env = NULL ;
935+ int need_detach = 0 ;
936+
937+ jint rc = (* hctx -> jvm )-> GetEnv (hctx -> jvm , (void * * )& env , JNI_VERSION_1_6 );
938+ if (rc == JNI_EDETACHED )
939+ {
940+ if ((* hctx -> jvm )-> AttachCurrentThread (hctx -> jvm , (void * * )& env , NULL ) != 0 )
941+ return -1 ;
942+ need_detach = 1 ;
943+ }
944+ else if (rc != JNI_OK )
945+ {
946+ return -1 ;
947+ }
948+
949+ jint ret = -1 ;
950+
951+ /* Find CommitOp class and constructor: CommitOp(byte[], byte[], long, boolean) */
952+ jclass commitOpClass = (* env )-> FindClass (env , "com/tidesdb/CommitOp" );
953+ if (commitOpClass == NULL )
954+ goto cleanup ;
955+
956+ jmethodID ctor = (* env )-> GetMethodID (env , commitOpClass , "<init>" , "([B[BJZ)V" );
957+ if (ctor == NULL )
958+ goto cleanup ;
959+
960+ /* Create CommitOp[] array */
961+ jobjectArray opsArray = (* env )-> NewObjectArray (env , num_ops , commitOpClass , NULL );
962+ if (opsArray == NULL )
963+ goto cleanup ;
964+
965+ for (int i = 0 ; i < num_ops ; i ++ )
966+ {
967+ jbyteArray jkey = (* env )-> NewByteArray (env , (jsize )ops [i ].key_size );
968+ (* env )-> SetByteArrayRegion (env , jkey , 0 , (jsize )ops [i ].key_size , (jbyte * )ops [i ].key );
969+
970+ jbyteArray jvalue = NULL ;
971+ if (ops [i ].value != NULL && ops [i ].value_size > 0 )
972+ {
973+ jvalue = (* env )-> NewByteArray (env , (jsize )ops [i ].value_size );
974+ (* env )-> SetByteArrayRegion (env , jvalue , 0 , (jsize )ops [i ].value_size ,
975+ (jbyte * )ops [i ].value );
976+ }
977+
978+ jobject opObj = (* env )-> NewObject (env , commitOpClass , ctor , jkey , jvalue ,
979+ (jlong )ops [i ].ttl ,
980+ ops [i ].is_delete ? JNI_TRUE : JNI_FALSE );
981+ (* env )-> SetObjectArrayElement (env , opsArray , i , opObj );
982+
983+ (* env )-> DeleteLocalRef (env , opObj );
984+ (* env )-> DeleteLocalRef (env , jkey );
985+ if (jvalue != NULL )
986+ (* env )-> DeleteLocalRef (env , jvalue );
987+ }
988+
989+ /* Call CommitHook.onCommit(CommitOp[], long) */
990+ jclass hookClass = (* env )-> GetObjectClass (env , hctx -> hook_obj );
991+ jmethodID onCommit =
992+ (* env )-> GetMethodID (env , hookClass , "onCommit" , "([Lcom/tidesdb/CommitOp;J)I" );
993+
994+ ret = (* env )-> CallIntMethod (env , hctx -> hook_obj , onCommit , opsArray , (jlong )commit_seq );
995+
996+ if ((* env )-> ExceptionCheck (env ))
997+ {
998+ (* env )-> ExceptionClear (env );
999+ ret = -1 ;
1000+ }
1001+
1002+ (* env )-> DeleteLocalRef (env , opsArray );
1003+ (* env )-> DeleteLocalRef (env , commitOpClass );
1004+ (* env )-> DeleteLocalRef (env , hookClass );
1005+
1006+ if (need_detach )
1007+ (* hctx -> jvm )-> DetachCurrentThread (hctx -> jvm );
1008+
1009+ return (int )ret ;
1010+
1011+ cleanup :
1012+ if ((* env )-> ExceptionCheck (env ))
1013+ (* env )-> ExceptionClear (env );
1014+ if (need_detach )
1015+ (* hctx -> jvm )-> DetachCurrentThread (hctx -> jvm );
1016+ return -1 ;
1017+ }
1018+
1019+ JNIEXPORT jlong JNICALL Java_com_tidesdb_ColumnFamily_nativeSetCommitHook (JNIEnv * env , jclass cls ,
1020+ jlong cfHandle ,
1021+ jobject hook ,
1022+ jlong oldCtxHandle )
1023+ {
1024+ tidesdb_column_family_t * cf = (tidesdb_column_family_t * )(uintptr_t )cfHandle ;
1025+
1026+ /* Free old context if present */
1027+ if (oldCtxHandle != 0 )
1028+ {
1029+ java_hook_ctx_t * old_ctx = (java_hook_ctx_t * )(uintptr_t )oldCtxHandle ;
1030+ (* env )-> DeleteGlobalRef (env , old_ctx -> hook_obj );
1031+ free (old_ctx );
1032+ }
1033+
1034+ /* If hook is NULL, clear the hook */
1035+ if (hook == NULL )
1036+ {
1037+ int result = tidesdb_cf_set_commit_hook (cf , NULL , NULL );
1038+ if (result != TDB_SUCCESS )
1039+ {
1040+ throwTidesDBException (env , result , getErrorMessage (result ));
1041+ }
1042+ return 0 ;
1043+ }
1044+
1045+ /* Allocate new context */
1046+ java_hook_ctx_t * ctx = (java_hook_ctx_t * )malloc (sizeof (java_hook_ctx_t ));
1047+ if (ctx == NULL )
1048+ {
1049+ throwTidesDBException (env , TDB_ERR_MEMORY , "Failed to allocate commit hook context" );
1050+ return 0 ;
1051+ }
1052+
1053+ (* env )-> GetJavaVM (env , & ctx -> jvm );
1054+ ctx -> hook_obj = (* env )-> NewGlobalRef (env , hook );
1055+
1056+ int result = tidesdb_cf_set_commit_hook (cf , java_commit_hook_trampoline , ctx );
1057+ if (result != TDB_SUCCESS )
1058+ {
1059+ (* env )-> DeleteGlobalRef (env , ctx -> hook_obj );
1060+ free (ctx );
1061+ throwTidesDBException (env , result , getErrorMessage (result ));
1062+ return 0 ;
1063+ }
1064+
1065+ return (jlong )(uintptr_t )ctx ;
1066+ }
1067+
9161068JNIEXPORT jdouble JNICALL Java_com_tidesdb_ColumnFamily_nativeRangeCost (JNIEnv * env , jclass cls ,
9171069 jlong handle ,
9181070 jbyteArray keyA ,
0 commit comments