@@ -160,14 +160,6 @@ 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-
171163template <typename ... Args>
172164static jobject callObjectMethodChecked (JNIEnv* env, jobject object, jmethodID method,
173165 Args... args) {
@@ -512,111 +504,6 @@ std::string dataTypeToString(lbug_data_type_id dataType) {
512504 }
513505}
514506
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-
620507void bindJavaParamsToPreparedStatement (JNIEnv* env, lbug_prepared_statement* preparedStatement,
621508 jobject javaMap) {
622509 jobject set = callObjectMethodChecked (env, javaMap, J_C_Map_M_entrySet);
@@ -627,31 +514,23 @@ void bindJavaParamsToPreparedStatement(JNIEnv* env, lbug_prepared_statement* pre
627514 jobject value = callObjectMethodChecked (env, entry, J_C_Map$Entry_M_getValue);
628515 std::string keyStr = jstringToUtf8String (env, key);
629516
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- }
517+ // The Java side (Connection.coerceParams) guarantees that every entry
518+ // is already a Value — boxed primitives are converted there before the
519+ // JNI call. We keep the IsInstanceOf check as a cheap contract guard:
520+ // if it ever fails, something bypassed the public API and we'd rather
521+ // fail loud than reinterpret_cast into the void.
522+ if (!env->IsInstanceOf (value, J_C_Value)) {
523+ env->DeleteLocalRef (entry);
524+ env->DeleteLocalRef (key);
525+ env->DeleteLocalRef (value);
526+ throwJNIException (env,
527+ (" Parameter '" + keyStr
528+ + " ' is not a Value — Connection.execute must be used as the entry point" )
529+ .c_str ());
530+ return ;
531+ }
532+
533+ lbug_value* clonedValue = lbug_value_clone (getValue (env, value));
655534
656535 auto state =
657536 lbug_prepared_statement_bind_value (preparedStatement, keyStr.c_str (), clonedValue);
0 commit comments