如何按需将PostgreSQL的Numeric类型解析为Rust原生类型
问题根因
你遇到的类型转换错误和feature配置不全、类型映射规则理解偏差直接相关:
- Rust的
i64/u64等原生整数类型,在postgrescrate中仅默认映射Postgres的BIGINT/INT8类型,不会自动匹配NUMERIC类型。因为NUMERIC是任意精度数值类型,可存储小数、也可存储远超原生整数范围的值,自动隐式转换存在溢出、精度丢失风险,官方没有做默认适配。 - 你看到文档中标注的整数类型转换支持,对应的就是Postgres原生整数类型,不是
NUMERIC,所以直接用i64接NUMERIC字段必然报错。 - 自定义包装类型实现
FromSql/ToSql不是必须方案,官方已经提供了现成的适配支持。
可用解决方案
方案1:开启官方feature使用精确数值类型(生产环境推荐,尤其适合价格场景)
你当前只开了with-chrono-0_4时间类型适配,缺少NUMERIC类型对应的适配feature,直接补上即可,不需要自己写转换逻辑:
- 调整
Cargo.toml依赖,开启rust_decimal适配feature(精确小数场景首选,无浮点精度误差):postgres = {version = "0.19.3", features = ["with-chrono-0_4", "with-rust_decimal-1"]} rust_decimal = "1" - 读取代码中替换对应字段类型即可:
use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use postgres::Client; // ... for row in client.query("SELECT * FROM pricetable;", &[])? { let time: DateTime<Utc> = row.get(0); let price: Decimal = row.get(1); // 如果你确定字段存的是整数值,可安全转成i64使用 let price_i64: i64 = price.try_into().expect("price value out of i64 range"); println!("found datum: {} {} ", time, price); }
如果需要处理超大范围的数值,也可以换成开启with-bigdecimal-0_3feature,对应使用bigdecimal::BigDecimal类型接收即可。
方案2:SQL侧做类型强转(快速调试/临时使用)
如果确定表中price字段存储的全是i64范围内的整数、没有小数,可以直接在查询语句中把NUMERIC字段转成BIGINT类型,Rust侧不需要改依赖、不需要换类型,直接用i64接收:
for row in client.query("SELECT time_col, price::BIGINT FROM pricetable;", &[])? { let time: DateTime<Utc> = row.get(0); let price: i64 = row.get(1); println!("found datum: {} {} ", time, price); }
注意:如果字段存在小数、或者数值超过i64取值范围,这个转换会直接在数据库侧抛出错误。
关于自定义类型实现trait的说明
这种方式只适合有特殊转换逻辑的场景,不是读取NUMERIC类型的唯一方案,官方提供的feature已经覆盖了绝大多数常规使用场景,没必要重复实现解析逻辑。如果确实有自定义需求,可以参考postgres crate内置的rust_decimal/bigdecimal适配的源码逻辑,按NUMERIC的二进制格式解析即可。
内容的提问来源于stack exchange,提问作者rtviii
相关产品推荐
相关产品推荐

