fix(tdengine): preserve FLOAT decimal precision

Closes #6937
This commit is contained in:
t8y2
2026-08-22 01:07:45 +00:00
parent da49e03048
commit b015cfeef5
2 changed files with 50 additions and 1 deletions
+46 -1
View File
@@ -18,7 +18,7 @@ pub fn borrowed_value_to_json(value: BorrowedValue<'_>, timezone: Option<Tz>) ->
BorrowedValue::USmallInt(value) => json!(value),
BorrowedValue::UInt(value) => json!(value),
BorrowedValue::UBigInt(value) => json!(value),
BorrowedValue::Float(value) => finite_number(value as f64),
BorrowedValue::Float(value) => finite_float_number(value),
BorrowedValue::Double(value) => finite_number(value),
BorrowedValue::VarChar(value) => Value::String(value.to_string()),
BorrowedValue::NChar(value) => Value::String(value.into_owned()),
@@ -38,6 +38,17 @@ fn finite_number(value: f64) -> Value {
Number::from_f64(value).map(Value::Number).unwrap_or_else(|| Value::String(value.to_string()))
}
fn finite_float_number(value: f32) -> Value {
let shortest = value.to_string();
if value.is_finite() {
// Widening f32 directly exposes binary approximation digits that were never significant in FLOAT.
let rounded = shortest.parse::<f64>().expect("a formatted finite f32 is a valid f64");
Value::Number(Number::from_f64(rounded).expect("a finite f32 is a finite f64"))
} else {
Value::String(shortest)
}
}
fn binary_json(value: &[u8]) -> Value {
json!({ "$binary": STANDARD.encode(value) })
}
@@ -86,6 +97,40 @@ mod tests {
use super::*;
use taos::Ty;
#[test]
fn serializes_float_with_shortest_round_trippable_decimal() {
let cases = [
(8.6_f32, "8.6"),
(0.72_f32, "0.72"),
(59.6_f32, "59.6"),
(-13.25_f32, "-13.25"),
(0.0_f32, "0.0"),
(-0.0_f32, "-0.0"),
(f32::from_bits(1), "1e-45"),
(f32::MIN_POSITIVE, "1.1754944e-38"),
(f32::MAX, "3.4028235e+38"),
(1.0e-7_f32, "1e-7"),
(1.0e20_f32, "1e+20"),
];
for (value, expected) in cases {
let encoded = borrowed_value_to_json(BorrowedValue::Float(value), None);
assert!(encoded.is_number(), "finite FLOAT must remain a JSON number");
assert_eq!(encoded.to_string(), expected);
assert_eq!((encoded.as_f64().unwrap() as f32).to_bits(), value.to_bits());
}
}
#[test]
fn keeps_float_fallback_and_other_numeric_types_unchanged() {
assert_eq!(borrowed_value_to_json(BorrowedValue::Float(f32::NAN), None), json!("NaN"));
assert_eq!(borrowed_value_to_json(BorrowedValue::Float(f32::INFINITY), None), json!("inf"));
assert_eq!(borrowed_value_to_json(BorrowedValue::Float(f32::NEG_INFINITY), None), json!("-inf"));
assert_eq!(borrowed_value_to_json(BorrowedValue::Null(Ty::Float), None), Value::Null);
assert_eq!(borrowed_value_to_json(BorrowedValue::Double(8.600000381469727), None), json!(8.600000381469727));
assert_eq!(borrowed_value_to_json(BorrowedValue::Int(42), None), json!(42));
}
#[test]
fn preserves_timestamp_precision_with_at_least_milliseconds() {
assert_eq!(
+4
View File
@@ -142,6 +142,8 @@ fn tdengine_websocket_live_compatibility() {
assert_eq!(query["rows"].as_array().unwrap().len(), 2);
assert_eq!(query["truncated"], true);
assert_eq!(query["rows"][0][0], "2026-08-04 10:00:00.001");
assert_eq!(query["column_types"][1], "FLOAT");
assert_eq!(query["rows"][0][1], 10.1);
let connection_info = agent.call("connection_info", json!({"agentSessionId": session_id}));
assert_eq!(connection_info["databaseInfo"]["currentDatabase"], database);
@@ -158,6 +160,7 @@ fn tdengine_websocket_live_compatibility() {
);
assert_eq!(first_page["has_more"], true);
assert_eq!(first_page["truncated"], false);
assert_eq!(first_page["rows"][0][1], 10.1);
let query_session_id = first_page["session_id"].as_str().expect("paged query session");
let final_page = agent.call(
"fetch_query_page",
@@ -169,6 +172,7 @@ fn tdengine_websocket_live_compatibility() {
}),
);
assert_eq!(final_page["rows"].as_array().unwrap().len(), 1);
assert_eq!(final_page["rows"][0][1], 10.2);
assert_eq!(final_page["has_more"], false);
assert_eq!(final_page["truncated"], true);