Skip to main content

mcp_sql/db/
convert.rs

1use serde_json::Value;
2use sqlx::any::AnyRow;
3use sqlx::{Column, Row, TypeInfo, ValueRef};
4
5/// Convert an AnyRow to a JSON object by inspecting column type info names.
6pub fn row_to_json(row: &AnyRow) -> Value {
7    let mut obj = serde_json::Map::new();
8
9    for col in row.columns() {
10        let name = col.name().to_string();
11        let ordinal = col.ordinal();
12        let type_name = col.type_info().name().to_uppercase();
13
14        let value = decode_column(row, ordinal, &type_name);
15        obj.insert(name, value);
16    }
17
18    Value::Object(obj)
19}
20
21fn decode_column(row: &AnyRow, ordinal: usize, type_name: &str) -> Value {
22    // Try NULL first
23    if let Ok(v) = row.try_get_raw(ordinal) {
24        if v.is_null() {
25            return Value::Null;
26        }
27    }
28
29    match type_name {
30        // Boolean types
31        "BOOL" | "BOOLEAN" => {
32            if let Ok(v) = row.try_get::<bool, _>(ordinal) {
33                return Value::Bool(v);
34            }
35        }
36
37        // Integer types
38        "INT2" | "SMALLINT" | "TINYINT" => {
39            if let Ok(v) = row.try_get::<i16, _>(ordinal) {
40                return Value::Number(v.into());
41            }
42        }
43        "INT" | "INT4" | "INTEGER" | "MEDIUMINT" => {
44            if let Ok(v) = row.try_get::<i32, _>(ordinal) {
45                return Value::Number(v.into());
46            }
47        }
48        "INT8" | "BIGINT" => {
49            if let Ok(v) = row.try_get::<i64, _>(ordinal) {
50                return Value::Number(v.into());
51            }
52        }
53
54        // Float types
55        "FLOAT4" | "REAL" | "FLOAT" => {
56            if let Ok(v) = row.try_get::<f64, _>(ordinal) {
57                return serde_json::Number::from_f64(v)
58                    .map(Value::Number)
59                    .unwrap_or(Value::Null);
60            }
61        }
62        "FLOAT8" | "DOUBLE" | "DOUBLE PRECISION" | "NUMERIC" | "DECIMAL" => {
63            if let Ok(v) = row.try_get::<f64, _>(ordinal) {
64                return serde_json::Number::from_f64(v)
65                    .map(Value::Number)
66                    .unwrap_or(Value::Null);
67            }
68        }
69
70        // Blob types
71        "BYTEA" | "BLOB" | "BINARY" | "VARBINARY" | "LONGBLOB" | "MEDIUMBLOB" | "TINYBLOB" => {
72            if let Ok(v) = row.try_get::<Vec<u8>, _>(ordinal) {
73                return Value::String(format!("(blob: {} bytes)", v.len()));
74            }
75        }
76
77        // Text/string types and everything else — fall through to the fallback below
78        _ => {}
79    }
80
81    // Fallback chain: try integer, float, bool, then string
82    if let Ok(v) = row.try_get::<i64, _>(ordinal) {
83        return Value::Number(v.into());
84    }
85    if let Ok(v) = row.try_get::<f64, _>(ordinal) {
86        if let Some(n) = serde_json::Number::from_f64(v) {
87            return Value::Number(n);
88        }
89    }
90    if let Ok(v) = row.try_get::<bool, _>(ordinal) {
91        return Value::Bool(v);
92    }
93    if let Ok(v) = row.try_get::<String, _>(ordinal) {
94        return Value::String(v);
95    }
96
97    Value::Null
98}