@@ -160,6 +160,14 @@ static void throwJNIException(JNIEnv* env, const char* message) {
160160 env->ThrowNew (exClass, message);
161161}
162162
163+ static void throwIllegalArgumentException (JNIEnv* env, const char * message) {
164+ jclass exClass = env->FindClass (" java/lang/IllegalArgumentException" );
165+ if (exClass == nullptr ) {
166+ return ;
167+ }
168+ env->ThrowNew (exClass, message);
169+ }
170+
163171template <typename ... Args>
164172static jobject callObjectMethodChecked (JNIEnv* env, jobject object, jmethodID method,
165173 Args... args) {
@@ -504,6 +512,111 @@ std::string dataTypeToString(lbug_data_type_id dataType) {
504512 }
505513}
506514
515+ // Convert a Java boxed primitive/String/UUID/etc. to an lbug_value*.
516+ // Returns nullptr if the type is not recognised.
517+ static lbug_value* javaObjectToValue (JNIEnv* env, jobject val) {
518+ if (env->IsInstanceOf (val, J_C_Boolean)) {
519+ jboolean value = callBooleanMethodChecked (env, val, J_C_Boolean_M_booleanValue);
520+ return lbug_value_create_bool (static_cast <bool >(value));
521+ } else if (env->IsInstanceOf (val, J_C_Byte)) {
522+ jbyte value = callByteMethodChecked (env, val, J_C_Byte_M_byteValue);
523+ return lbug_value_create_int8 (static_cast <int8_t >(value));
524+ } else if (env->IsInstanceOf (val, J_C_Short)) {
525+ jshort value = callShortMethodChecked (env, val, J_C_Short_M_shortValue);
526+ return lbug_value_create_int16 (static_cast <int16_t >(value));
527+ } else if (env->IsInstanceOf (val, J_C_Integer)) {
528+ jint value = callIntMethodChecked (env, val, J_C_Integer_M_intValue);
529+ return lbug_value_create_int32 (static_cast <int32_t >(value));
530+ } else if (env->IsInstanceOf (val, J_C_Long)) {
531+ jlong value = callLongMethodChecked (env, val, J_C_Long_M_longValue);
532+ return lbug_value_create_int64 (static_cast <int64_t >(value));
533+ } else if (env->IsInstanceOf (val, J_C_BigInteger)) {
534+ int64_t lower =
535+ static_cast <int64_t >(callLongMethodChecked (env, val, J_C_BigInteger_M_longValue));
536+ jobject shifted = callObjectMethodChecked (env, val, J_C_BigInteger_M_shiftRight, 64 );
537+ int64_t upper = static_cast <int64_t >(
538+ callLongMethodChecked (env, shifted, J_C_BigInteger_M_longValue));
539+ return lbug_value_create_int128 ({.low = static_cast <uint64_t >(lower), .high = upper});
540+ } else if (env->IsInstanceOf (val, J_C_Float)) {
541+ jfloat value = callFloatMethodChecked (env, val, J_C_Float_M_floatValue);
542+ return lbug_value_create_float (static_cast <float >(value));
543+ } else if (env->IsInstanceOf (val, J_C_Double)) {
544+ jdouble value = callDoubleMethodChecked (env, val, J_C_Double_M_doubleValue);
545+ return lbug_value_create_double (static_cast <double >(value));
546+ } else if (env->IsInstanceOf (val, J_C_BigDecimal)) {
547+ jobject normalized =
548+ callObjectMethodChecked (env, val, J_C_BigDecimal_M_stripTrailingZeros);
549+ jstring value = static_cast <jstring>(
550+ callObjectMethodChecked (env, normalized, J_C_BigDecimal_M_toString));
551+ std::string str = jstringToUtf8String (env, value);
552+ auto precision = static_cast <int32_t >(
553+ callIntMethodChecked (env, normalized, J_C_BigDecimal_M_precision));
554+ auto scale =
555+ static_cast <int32_t >(callIntMethodChecked (env, normalized, J_C_BigDecimal_M_scale));
556+ if (precision > JAVA_DECIMAL_PRECISION_LIMIT ) {
557+ throw NotImplementedException (
558+ std::format (" Decimal precision cannot be greater than {}"
559+ " Note: positive exponents contribute to precision" ,
560+ JAVA_DECIMAL_PRECISION_LIMIT ));
561+ }
562+ return lbug_value_create_decimal (
563+ str.c_str (), static_cast <uint32_t >(precision), static_cast <uint32_t >(scale));
564+ } else if (env->IsInstanceOf (val, J_C_String)) {
565+ jstring value = static_cast <jstring>(val);
566+ std::string str = jstringToUtf8String (env, value);
567+ return lbug_value_create_string (str.c_str ());
568+ } else if (env->IsInstanceOf (val, J_C_InternalID)) {
569+ return lbug_value_create_internal_id (getInternalID (env, val));
570+ } else if (env->IsInstanceOf (val, J_C_UUID )) {
571+ jstring uuid =
572+ static_cast <jstring>(callObjectMethodChecked (env, val, J_C_UUID_M_toString));
573+ std::string uuidString = jstringToUtf8String (env, uuid);
574+ return lbug_value_create_uuid (uuidString.c_str ());
575+ } else if (env->IsInstanceOf (val, J_C_LocalDate)) {
576+ int64_t days =
577+ static_cast <int64_t >(callLongMethodChecked (env, val, J_C_LocalDate_M_toEpochDay));
578+ return lbug_value_create_date ({.days = static_cast <int32_t >(days)});
579+ } else if (env->IsInstanceOf (val, J_C_Instant)) {
580+ // TODO: Need to review this for overflow
581+ int64_t seconds = static_cast <int64_t >(
582+ callLongMethodChecked (env, val, J_C_LocalDate_M_getEpochSecond));
583+ int64_t nano =
584+ static_cast <int64_t >(callLongMethodChecked (env, val, J_C_LocalDate_M_getNano));
585+ int64_t micro = (seconds * 1000000L ) + (nano / 1000L );
586+ return lbug_value_create_timestamp ({.value = micro});
587+ } else if (env->IsInstanceOf (val, J_C_Duration)) {
588+ auto milis = callLongMethodChecked (env, val, J_C_Duration_M_toMillis);
589+ return lbug_value_create_interval ({.months = 0 , .days = 0 , .micros = milis * 1000L });
590+ }
591+ return nullptr ;
592+ }
593+
594+ // Get the fully-qualified class name of a Java object as a std::string.
595+ // Returns "<unknown>" on error.
596+ static std::string getClassName (JNIEnv* env, jobject obj) {
597+ jclass clazz = env->GetObjectClass (obj);
598+ if (clazz == nullptr ) return " <unknown>" ;
599+ jclass classClass = env->FindClass (" java/lang/Class" );
600+ if (classClass == nullptr ) {
601+ env->DeleteLocalRef (clazz);
602+ return " <unknown>" ;
603+ }
604+ jmethodID getNameMethod =
605+ env->GetMethodID (classClass, " getName" , " ()Ljava/lang/String;" );
606+ if (getNameMethod == nullptr ) {
607+ env->DeleteLocalRef (classClass);
608+ env->DeleteLocalRef (clazz);
609+ return " <unknown>" ;
610+ }
611+ jstring classNameJStr =
612+ static_cast <jstring>(env->CallObjectMethod (clazz, getNameMethod));
613+ std::string result = jstringToUtf8String (env, classNameJStr);
614+ env->DeleteLocalRef (classNameJStr);
615+ env->DeleteLocalRef (classClass);
616+ env->DeleteLocalRef (clazz);
617+ return result;
618+ }
619+
507620void bindJavaParamsToPreparedStatement (JNIEnv* env, lbug_prepared_statement* preparedStatement,
508621 jobject javaMap) {
509622 jobject set = callObjectMethodChecked (env, javaMap, J_C_Map_M_entrySet);
@@ -513,7 +626,33 @@ void bindJavaParamsToPreparedStatement(JNIEnv* env, lbug_prepared_statement* pre
513626 jstring key = (jstring)callObjectMethodChecked (env, entry, J_C_Map$Entry_M_getKey);
514627 jobject value = callObjectMethodChecked (env, entry, J_C_Map$Entry_M_getValue);
515628 std::string keyStr = jstringToUtf8String (env, key);
516- auto * clonedValue = lbug_value_clone (getValue (env, value));
629+
630+ lbug_value* clonedValue = nullptr ;
631+
632+ if (env->IsInstanceOf (value, J_C_Value)) {
633+ // Already a Value — use the direct JNI path (Option A, existing behaviour)
634+ clonedValue = lbug_value_clone (getValue (env, value));
635+ } else {
636+ // Try conversion from a boxed Java type via the same ladder as
637+ // Java_com_ladybugdb_Native_lbugValueCreateValue (Option A)
638+ clonedValue = javaObjectToValue (env, value);
639+ if (clonedValue == nullptr ) {
640+ // Not a recognised type — throw IllegalArgumentException instead
641+ // of crashing (Option B)
642+ std::string typeName = getClassName (env, value);
643+ env->DeleteLocalRef (entry);
644+ env->DeleteLocalRef (key);
645+ env->DeleteLocalRef (value);
646+ throwIllegalArgumentException (env,
647+ (" Parameter '" + keyStr + " ' has unsupported type " + typeName
648+ + " . Accepted types: Value, Boolean, Byte, Short, Integer, "
649+ " Long, BigInteger, Float, Double, BigDecimal, String, "
650+ " InternalID, UUID, LocalDate, Instant, Duration" )
651+ .c_str ());
652+ return ;
653+ }
654+ }
655+
517656 auto state =
518657 lbug_prepared_statement_bind_value (preparedStatement, keyStr.c_str (), clonedValue);
519658 lbug_value_destroy (clonedValue);
@@ -769,6 +908,9 @@ JNIEXPORT jobject JNICALL Java_com_ladybugdb_Native_lbugConnectionExecute(JNIEnv
769908 auto * conn = getConnection (env, thisConn);
770909 auto * ps = getPreparedStatement (env, preStm);
771910 bindJavaParamsToPreparedStatement (env, ps, paramMap);
911+ if (env->ExceptionCheck ()) {
912+ return jobject ();
913+ }
772914 auto * queryResult = new lbug_query_result ();
773915 lbug_state state = lbug_connection_execute (conn, ps, queryResult);
774916 if (state != LbugSuccess && queryResult->_query_result == nullptr ) {
0 commit comments