use json::JsonValue;
use log::warn;
#[derive(Clone, Debug)]
pub enum FieldFormat {
Str,
Text,
Int,
BigInt,
SmallInt,
Boolean,
Float,
Double,
Decimal,
Json,
Timestamp,
Money,
Date,
Time,
DateTime,
Interval,
Bytea,
Uuid,
Inet,
Cidr,
MacAddr,
Bit,
Array,
Xml,
Oid,
None,
}
impl FieldFormat {
pub fn from_u16(code: u16, type_oid: u32) -> FieldFormat {
match (code, type_oid) {
(0, 25) => FieldFormat::Text,
(0, 1043 | 1042 | 19 | 18) => FieldFormat::Str,
(0, 16) => FieldFormat::Boolean,
(0, 700) => FieldFormat::Float,
(0, 701) => FieldFormat::Double,
(0, 790) => FieldFormat::Money,
(0, 1700) => FieldFormat::Decimal,
(0, 21) => FieldFormat::SmallInt,
(0, 23) => FieldFormat::Int,
(0, 20) => FieldFormat::BigInt,
(0, 114 | 3802) => FieldFormat::Json,
(0, 1082) => FieldFormat::Date,
(0, 1114 | 1184) => FieldFormat::Timestamp,
(0, 1083 | 1266) => FieldFormat::Time,
(0, 1186) => FieldFormat::Interval,
(0, 2950) => FieldFormat::Uuid,
(0, 17) => FieldFormat::Bytea,
(0, 869) => FieldFormat::Inet,
(0, 650) => FieldFormat::Cidr,
(0, 829 | 774) => FieldFormat::MacAddr,
(0, 1560 | 1562) => FieldFormat::Bit,
(0, 142) => FieldFormat::Xml,
(0, 26) => FieldFormat::Oid,
(
0,
1007 | 1009 | 1016 | 1005 | 1021 | 1022 | 1000 | 1015 | 1014 | 1182 | 1115 | 1185
| 1183 | 1270 | 199 | 3807,
) => FieldFormat::Array,
_ => {
warn!("æœªå®žçŽ°å—æ®µç±»åž‹: {code} {type_oid}");
FieldFormat::None
}
}
}
pub fn from_value(format: &FieldFormat, data: Vec<u8>) -> JsonValue {
if data.is_empty() {
return match format {
FieldFormat::Str | FieldFormat::Text | FieldFormat::Uuid | FieldFormat::Xml => {
"".into()
}
FieldFormat::SmallInt | FieldFormat::Int => 0_i32.into(),
FieldFormat::BigInt | FieldFormat::Oid => 0_i64.into(),
FieldFormat::Boolean => false.into(),
FieldFormat::Float => 0_f32.into(),
FieldFormat::Double | FieldFormat::Decimal => 0_f64.into(),
FieldFormat::Timestamp
| FieldFormat::Money
| FieldFormat::Date
| FieldFormat::Time
| FieldFormat::DateTime
| FieldFormat::Interval => "".into(),
FieldFormat::Inet | FieldFormat::Cidr | FieldFormat::MacAddr | FieldFormat::Bit => {
"".into()
}
FieldFormat::Bytea => "".into(),
FieldFormat::Array => JsonValue::Array(vec![]),
FieldFormat::Json => JsonValue::Null,
FieldFormat::None => JsonValue::Null,
};
}
let text = String::from_utf8_lossy(&data);
match format {
FieldFormat::Str | FieldFormat::Text | FieldFormat::Uuid | FieldFormat::Xml => {
text.into_owned().into()
}
FieldFormat::SmallInt | FieldFormat::Int => text.parse::<i32>().unwrap_or(0).into(),
FieldFormat::BigInt | FieldFormat::Oid => text.parse::<i64>().unwrap_or(0).into(),
FieldFormat::Boolean => (data.first().copied().unwrap_or(0) == b't').into(),
FieldFormat::Float => text.parse::<f32>().unwrap_or(0.0).into(),
FieldFormat::Double | FieldFormat::Decimal => text.parse::<f64>().unwrap_or(0.0).into(),
FieldFormat::Timestamp
| FieldFormat::Money
| FieldFormat::Date
| FieldFormat::Time
| FieldFormat::DateTime
| FieldFormat::Interval => text.into_owned().into(),
FieldFormat::Inet | FieldFormat::Cidr | FieldFormat::MacAddr | FieldFormat::Bit => {
text.into_owned().into()
}
FieldFormat::Bytea => text.into_owned().into(),
FieldFormat::Array => parse_pg_array(&text),
FieldFormat::Json => json::parse(&text).unwrap_or(JsonValue::Null),
FieldFormat::None => JsonValue::Null,
}
}
}
fn parse_pg_array(text: &str) -> JsonValue {
let trimmed = text.trim();
if !trimmed.starts_with('{') || !trimmed.ends_with('}') {
return JsonValue::Array(vec![]);
}
let inner = &trimmed[1..trimmed.len() - 1];
if inner.is_empty() {
return JsonValue::Array(vec![]);
}
let mut result = vec![];
let mut current = String::new();
let mut in_quotes = false;
let mut escape_next = false;
let mut depth = 0;
for ch in inner.chars() {
if escape_next {
current.push(ch);
escape_next = false;
continue;
}
match ch {
'\\' => {
escape_next = true;
current.push(ch);
}
'"' => {
in_quotes = !in_quotes;
current.push(ch);
}
'{' => {
depth += 1;
current.push(ch);
}
'}' => {
depth -= 1;
current.push(ch);
}
',' if !in_quotes && depth == 0 => {
result.push(parse_array_element(¤t));
current.clear();
}
_ => {
current.push(ch);
}
}
}
if !current.is_empty() {
result.push(parse_array_element(¤t));
}
JsonValue::Array(result)
}
fn parse_array_element(s: &str) -> JsonValue {
let trimmed = s.trim();
if trimmed == "NULL" {
return JsonValue::Null;
}
if trimmed.starts_with('"') && trimmed.ends_with('"') && trimmed.len() >= 2 {
let unquoted = &trimmed[1..trimmed.len() - 1];
return unquoted.replace("\\\"", "\"").replace("\\\\", "\\").into();
}
if trimmed.starts_with('{') {
return parse_pg_array(trimmed);
}
if let Ok(i) = trimmed.parse::<i64>() {
return i.into();
}
if let Ok(f) = trimmed.parse::<f64>() {
return f.into();
}
if trimmed == "t" || trimmed == "true" {
return true.into();
}
if trimmed == "f" || trimmed == "false" {
return false.into();
}
trimmed.into()
}
#[cfg(test)]
mod tests {
use super::{parse_array_element, parse_pg_array, FieldFormat};
use json::JsonValue;
use std::mem::discriminant;
fn j(raw: &str) -> JsonValue {
json::parse(raw).expect("valid json")
}
fn assert_variant(code: u16, type_oid: u32, expected: FieldFormat) {
let actual = FieldFormat::from_u16(code, type_oid);
assert_eq!(
discriminant(&actual),
discriminant(&expected),
"unexpected format for code={code}, type_oid={type_oid}: {actual:?}"
);
}
#[test]
fn from_u16_single_oid_mappings() {
assert_variant(0, 25, FieldFormat::Text);
assert_variant(0, 16, FieldFormat::Boolean);
assert_variant(0, 700, FieldFormat::Float);
assert_variant(0, 701, FieldFormat::Double);
assert_variant(0, 790, FieldFormat::Money);
assert_variant(0, 1700, FieldFormat::Decimal);
assert_variant(0, 21, FieldFormat::SmallInt);
assert_variant(0, 23, FieldFormat::Int);
assert_variant(0, 20, FieldFormat::BigInt);
assert_variant(0, 1082, FieldFormat::Date);
assert_variant(0, 1186, FieldFormat::Interval);
assert_variant(0, 2950, FieldFormat::Uuid);
assert_variant(0, 17, FieldFormat::Bytea);
assert_variant(0, 869, FieldFormat::Inet);
assert_variant(0, 650, FieldFormat::Cidr);
assert_variant(0, 142, FieldFormat::Xml);
assert_variant(0, 26, FieldFormat::Oid);
}
#[test]
fn from_u16_multi_oid_mappings() {
for oid in [1043_u32, 1042, 19, 18] {
assert_variant(0, oid, FieldFormat::Str);
}
for oid in [114_u32, 3802] {
assert_variant(0, oid, FieldFormat::Json);
}
for oid in [1114_u32, 1184] {
assert_variant(0, oid, FieldFormat::Timestamp);
}
for oid in [1083_u32, 1266] {
assert_variant(0, oid, FieldFormat::Time);
}
for oid in [829_u32, 774] {
assert_variant(0, oid, FieldFormat::MacAddr);
}
for oid in [1560_u32, 1562] {
assert_variant(0, oid, FieldFormat::Bit);
}
for oid in [
1007_u32, 1009, 1016, 1005, 1021, 1022, 1000, 1015, 1014, 1182, 1115, 1185, 1183, 1270,
199, 3807,
] {
assert_variant(0, oid, FieldFormat::Array);
}
}
#[test]
fn from_u16_unknown_fallback() {
assert_variant(0, 9999, FieldFormat::None);
assert_variant(1, 25, FieldFormat::None);
}
#[test]
fn from_value_empty_data_returns_default() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Str, Vec::new()),
j(r#""""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Text, Vec::new()),
j(r#""""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Int, Vec::new()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::BigInt, Vec::new()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::SmallInt, Vec::new()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Boolean, Vec::new()),
j("false")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Float, Vec::new()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Double, Vec::new()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Decimal, Vec::new()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Date, Vec::new()),
j(r#""""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Array, Vec::new()),
j("[]")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Json, Vec::new()),
JsonValue::Null
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::None, Vec::new()),
JsonValue::Null
);
}
#[test]
fn from_value_string_like_variants() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Str, b"hello".to_vec()),
j(r#""hello""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Text, b"world".to_vec()),
j(r#""world""#)
);
assert_eq!(
FieldFormat::from_value(
&FieldFormat::Uuid,
b"550e8400-e29b-41d4-a716-446655440000".to_vec(),
),
j(r#""550e8400-e29b-41d4-a716-446655440000""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Xml, b"<x/>".to_vec()),
j(r#""<x/>""#)
);
}
#[test]
fn from_value_i32_variants_with_fallback() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::SmallInt, b"12".to_vec()),
j("12")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::SmallInt, b"bad".to_vec()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Int, b"123".to_vec()),
j("123")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Int, b"bad".to_vec()),
j("0")
);
}
#[test]
fn from_value_i64_variants_with_fallback() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::BigInt, b"922337203685477580".to_vec()),
j("922337203685477580")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::BigInt, b"bad".to_vec()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Oid, b"26".to_vec()),
j("26")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Oid, b"bad".to_vec()),
j("0")
);
}
#[test]
fn from_value_boolean_variants() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Boolean, b"t".to_vec()),
j("true")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Boolean, b"f".to_vec()),
j("false")
);
}
#[test]
fn from_value_float_variants() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Float, b"1.5".to_vec()),
j("1.5")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Float, b"bad".to_vec()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Double, b"2.75".to_vec()),
j("2.75")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Double, b"bad".to_vec()),
j("0")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Decimal, b"9.99".to_vec()),
j("9.99")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Decimal, b"bad".to_vec()),
j("0")
);
}
#[test]
fn from_value_temporal_and_money_variants() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Timestamp, b"2025-01-01 10:11:12".to_vec()),
j(r#""2025-01-01 10:11:12""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Money, b"$12.30".to_vec()),
j(r#""$12.30""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Date, b"2025-01-01".to_vec()),
j(r#""2025-01-01""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Time, b"10:11:12".to_vec()),
j(r#""10:11:12""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::DateTime, b"2025-01-01T10:11:12".to_vec()),
j(r#""2025-01-01T10:11:12""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Interval, b"1 day".to_vec()),
j(r#""1 day""#)
);
}
#[test]
fn from_value_network_and_binary_variants() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Inet, b"127.0.0.1".to_vec()),
j(r#""127.0.0.1""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Cidr, b"10.0.0.0/8".to_vec()),
j(r#""10.0.0.0/8""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::MacAddr, b"08:00:2b:01:02:03".to_vec()),
j(r#""08:00:2b:01:02:03""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Bit, b"1010".to_vec()),
j(r#""1010""#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Bytea, b"\\xDEADBEEF".to_vec()),
j(r#""\\xDEADBEEF""#)
);
}
#[test]
fn from_value_array_json_and_none_variants() {
assert_eq!(
FieldFormat::from_value(&FieldFormat::Array, b"{1,2,3}".to_vec()),
j("[1,2,3]")
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Json, b"{\"a\":1}".to_vec()),
j(r#"{"a":1}"#)
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::Json, b"{a:1}".to_vec()),
JsonValue::Null
);
assert_eq!(
FieldFormat::from_value(&FieldFormat::None, b"anything".to_vec()),
JsonValue::Null
);
}
#[test]
fn parse_pg_array_empty_and_invalid() {
assert_eq!(parse_pg_array("{}"), j("[]"));
assert_eq!(parse_pg_array("1,2,3"), j("[]"));
}
#[test]
fn parse_pg_array_simple_integers() {
assert_eq!(parse_pg_array("{1,2,3}"), j("[1,2,3]"));
}
#[test]
fn parse_pg_array_strings_with_quotes() {
assert_eq!(
parse_pg_array(r#"{"hello","world"}"#),
j(r#"["hello","world"]"#)
);
}
#[test]
fn parse_pg_array_with_null_elements() {
assert_eq!(parse_pg_array("{1,NULL,3}"), j("[1,null,3]"));
}
#[test]
fn parse_pg_array_nested_arrays() {
assert_eq!(parse_pg_array("{{1,2},{3,4}}"), j("[[1,2],[3,4]]"));
}
#[test]
fn parse_pg_array_escaped_characters() {
assert_eq!(
parse_pg_array(r#"{"he\"llo","wo\\rld"}"#),
j(r#"["he\"llo","wo\\rld"]"#)
);
}
#[test]
fn parse_pg_array_booleans() {
assert_eq!(
parse_pg_array("{t,f,true,false}"),
j("[true,false,true,false]")
);
}
#[test]
fn parse_pg_array_floats() {
assert_eq!(parse_pg_array("{1.5,2.7}"), j("[1.5,2.7]"));
}
#[test]
fn parse_pg_array_plain_strings() {
assert_eq!(parse_pg_array("{abc,def}"), j(r#"["abc","def"]"#));
}
#[test]
fn parse_array_element_variants() {
assert_eq!(parse_array_element("NULL"), JsonValue::Null);
assert_eq!(parse_array_element(r#""he\"llo""#), j(r#""he\"llo""#));
assert_eq!(parse_array_element("{1,2}"), j("[1,2]"));
assert_eq!(parse_array_element("42"), j("42"));
assert_eq!(parse_array_element("3.14"), j("3.14"));
assert_eq!(parse_array_element("t"), j("true"));
assert_eq!(parse_array_element("true"), j("true"));
assert_eq!(parse_array_element("f"), j("false"));
assert_eq!(parse_array_element("false"), j("false"));
assert_eq!(parse_array_element("plain"), j(r#""plain""#));
}
}