diff --git a/api/src/utils.rs b/api/src/utils.rs index 2c48ac1..e046ea2 100644 --- a/api/src/utils.rs +++ b/api/src/utils.rs @@ -17,42 +17,64 @@ pub fn get_column_names(rows: &[Row]) -> Vec { } } +macro_rules! get_json_option { + ($row:expr, $idx:expr, $type:ty) => { + { + let v: Option<$type> = $row.try_get($idx).unwrap_or(None); + json!(v) + } + }; +} + pub fn rows_to_json(rows: &[Row]) -> Vec { + if rows.is_empty() { + return vec![]; + } + + let columns = rows[0].columns(); + let column_types: Vec<&Type> = columns.iter().map(|col| col.type_()).collect(); + let column_names: Vec<&str> = columns.iter().map(|col| col.name()).collect(); + rows.iter() .map(|row| { let mut map = serde_json::Map::new(); - for (i, column) in row.columns().iter().enumerate() { - let value: Value = match *column.type_() { - Type::INT2 => json!(row.get::<_, i16>(i)), - Type::INT4 => json!(row.get::<_, i32>(i)), - Type::INT8 => json!(row.get::<_, i64>(i)), - Type::FLOAT4 => json!(row.get::<_, f32>(i)), - Type::FLOAT8 => json!(row.get::<_, f64>(i)), - Type::BOOL => json!(row.get::<_, bool>(i)), - Type::VARCHAR | Type::TEXT | Type::BPCHAR => json!(row.get::<_, String>(i)), + for (i, &column_type) in column_types.iter().enumerate() { + let column_name = column_names[i]; + + let value: Value = match *column_type { + Type::INT2 => get_json_option!(row, i, i16), + Type::INT4 => get_json_option!(row, i, i32), + Type::INT8 => get_json_option!(row, i, i64), + Type::FLOAT4 => get_json_option!(row, i, f32), + Type::FLOAT8 => get_json_option!(row, i, f64), + Type::BOOL => get_json_option!(row, i, bool), + Type::VARCHAR | Type::TEXT | Type::BPCHAR => get_json_option!(row, i, String), Type::TIMESTAMP => { - let ts: NaiveDateTime = row.get(i); - json!(ts.to_string()) + let v: Option = row.try_get(i).unwrap_or(None); + v.map(|ts| json!(ts.to_string())).unwrap_or(Value::Null) } Type::TIMESTAMPTZ => { - let ts: DateTime = row.get(i); - json!(ts.to_rfc3339()) + let v: Option> = row.try_get(i).unwrap_or(None); + v.map(|ts| json!(ts.to_rfc3339())).unwrap_or(Value::Null) } Type::DATE => { - let date: NaiveDate = row.get(i); - json!(date.to_string()) + let v: Option = row.try_get(i).unwrap_or(None); + v.map(|date| json!(date.to_string())).unwrap_or(Value::Null) } Type::JSON | Type::JSONB => { - let json_value: serde_json::Value = row.get(i); - json_value + let v: Option = row.try_get(i).unwrap_or(None); + v.unwrap_or(Value::Null) } Type::UUID => { - let uuid: Uuid = row.get(i); - json!(uuid.to_string()) + let v: Option = row.try_get(i).unwrap_or(None); + v.map(|uuid| json!(uuid.to_string())).unwrap_or(Value::Null) + } + _ => { + log::warn!("Unhandled column type: {:?}", column_type); + Value::Null } - _ => Value::Null, }; - map.insert(column.name().to_string(), value); + map.insert(column_name.to_string(), value); } Value::Object(map) }) @@ -69,7 +91,7 @@ pub struct Pagination { impl Pagination { pub fn new(query: Query, total_count: i64) -> Self { let limit = query.limit.unwrap_or(200).clamp(1, 1000); - let total_pages = (total_count as f64 / limit as f64).ceil() as i64; + let total_pages = ((total_count as f64 / limit as f64).ceil() as i64).max(1); let page = query.page.unwrap_or(1).clamp(1, total_pages);