From 75244339e4828c31ffe92372547f44e1234f92be Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Fri, 1 May 2026 00:13:42 +0800 Subject: [PATCH] feat(postgres): enhance JSON serialization for temporal and numeric types --- src-tauri/src/commands/query_cancel.rs | 1 + src-tauri/src/db/postgres.rs | 102 ++++++++++++++++++++----- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/src-tauri/src/commands/query_cancel.rs b/src-tauri/src/commands/query_cancel.rs index bdb81ae2a6..fbb1c5ef35 100644 --- a/src-tauri/src/commands/query_cancel.rs +++ b/src-tauri/src/commands/query_cancel.rs @@ -38,6 +38,7 @@ impl RunningQueries { } } + #[cfg(test)] pub fn has(&self, execution_id: &str) -> bool { self.inner .lock() diff --git a/src-tauri/src/db/postgres.rs b/src-tauri/src/db/postgres.rs index 190ca2ff8f..ea32b1a16b 100644 --- a/src-tauri/src/db/postgres.rs +++ b/src-tauri/src/db/postgres.rs @@ -1,9 +1,80 @@ +use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}; use sqlx::postgres::{PgPool, PgPoolOptions, PgRow}; -use sqlx::{Column, Executor, Row}; +use sqlx::{Column, Executor, Row, TypeInfo, ValueRef}; use std::time::{Duration, Instant}; use super::{ColumnInfo, DatabaseInfo, ForeignKeyInfo, IndexInfo, QueryResult, TableInfo, TriggerInfo}; +fn pg_temporal_to_json_value(row: &PgRow, idx: usize) -> Option { + if let Ok(v) = row.try_get::, _>(idx) { + return Some(serde_json::Value::String(v.to_rfc3339())); + } + if let Ok(v) = row.try_get::(idx) { + return Some(serde_json::Value::String(v.to_string())); + } + if let Ok(v) = row.try_get::(idx) { + return Some(serde_json::Value::String(v.to_string())); + } + if let Ok(v) = row.try_get::(idx) { + return Some(serde_json::Value::String(v.to_string())); + } + None +} + +fn pg_value_to_json(row: &PgRow, idx: usize, type_name: &str) -> serde_json::Value { + if row.try_get_raw(idx).map(|v| v.is_null()).unwrap_or(true) { + return serde_json::Value::Null; + } + + let upper = type_name.to_uppercase(); + + if upper == "BOOL" { + return row + .try_get::(idx) + .map(serde_json::Value::Bool) + .unwrap_or(serde_json::Value::Null); + } + + if upper.contains("TIMESTAMP") + || upper == "DATE" + || upper == "TIME" + || upper == "TIMETZ" + || upper.contains("INTERVAL") + { + if let Some(v) = pg_temporal_to_json_value(row, idx) { + return v; + } + } + + if upper == "NUMERIC" || upper == "DECIMAL" || upper == "MONEY" { + return row + .try_get_unchecked::(idx) + .map(serde_json::Value::String) + .unwrap_or(serde_json::Value::Null); + } + + row.try_get::(idx) + .map(serde_json::Value::String) + .or_else(|_| { + row.try_get::(idx) + .map(|v| serde_json::Value::Number(v.into())) + }) + .or_else(|_| { + row.try_get::(idx) + .map(|v| serde_json::Value::Number(v.into())) + }) + .or_else(|_| { + row.try_get::(idx).map(|v| { + serde_json::Number::from_f64(v) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null) + }) + }) + .or_else(|_| row.try_get::(idx).map(serde_json::Value::Bool)) + .or_else(|e| pg_temporal_to_json_value(row, idx).ok_or(e)) + .unwrap_or(serde_json::Value::Null) +} + pub async fn connect(url: &str) -> Result { PgPoolOptions::new() .max_connections(5) @@ -119,6 +190,11 @@ pub async fn execute_query(pool: &PgPool, sql: &str) -> Result = desc.columns().iter().map(|c| c.name().to_string()).collect(); + let column_types: Vec = desc + .columns() + .iter() + .map(|c| c.type_info().name().to_string()) + .collect(); let rows: Vec = sqlx::query(sql) .fetch_all(pool) @@ -130,25 +206,11 @@ pub async fn execute_query(pool: &PgPool, sql: &str) -> Result(i) - .map(serde_json::Value::String) - .or_else(|_| { - row.try_get::(i) - .map(|v| serde_json::Value::Number(v.into())) - }) - .or_else(|_| { - row.try_get::(i) - .map(|v| serde_json::Value::Number(v.into())) - }) - .or_else(|_| { - row.try_get::(i).map(|v| { - serde_json::Number::from_f64(v) - .map(serde_json::Value::Number) - .unwrap_or(serde_json::Value::Null) - }) - }) - .or_else(|_| row.try_get::(i).map(serde_json::Value::Bool)) - .unwrap_or(serde_json::Value::Null) + pg_value_to_json( + row, + i, + column_types.get(i).map(String::as_str).unwrap_or(""), + ) }) .collect() })