pub fn resolve_rust_type(oid: u32) -> Result<&'static str, String> {
if let Some(t) = bsql_core::types::rust_type_for_oid(oid) {
return Ok(t);
}
match oid {
1184 => {
if cfg!(feature = "time") {
Ok("::time::OffsetDateTime")
} else if cfg!(feature = "chrono") {
Ok("::chrono::DateTime<::chrono::Utc>")
} else {
Err(feature_error("TIMESTAMPTZ", oid, &["time", "chrono"]))
}
}
1114 => {
if cfg!(feature = "time") {
Ok("::time::PrimitiveDateTime")
} else if cfg!(feature = "chrono") {
Ok("::chrono::NaiveDateTime")
} else {
Err(feature_error("TIMESTAMP", oid, &["time", "chrono"]))
}
}
1082 => {
if cfg!(feature = "time") {
Ok("::time::Date")
} else if cfg!(feature = "chrono") {
Ok("::chrono::NaiveDate")
} else {
Err(feature_error("DATE", oid, &["time", "chrono"]))
}
}
1083 => {
if cfg!(feature = "time") {
Ok("::time::Time")
} else if cfg!(feature = "chrono") {
Ok("::chrono::NaiveTime")
} else {
Err(feature_error("TIME", oid, &["time", "chrono"]))
}
}
1266 => Err(format!(
"column type is TIMETZ (OID {oid}) which is not supported by bsql. \
Use TIME or TIMESTAMPTZ instead."
)),
2950 => {
if cfg!(feature = "uuid") {
Ok("::uuid::Uuid")
} else {
Err(feature_error("UUID", oid, &["uuid"]))
}
}
1700 => {
if cfg!(feature = "decimal") {
Ok("::rust_decimal::Decimal")
} else {
Err(feature_error("NUMERIC", oid, &["decimal"]))
}
}
1115 => resolve_array("TIMESTAMP[]", 1114, oid),
1185 => resolve_array("TIMESTAMPTZ[]", 1184, oid),
1182 => resolve_array("DATE[]", 1082, oid),
1183 => resolve_array("TIME[]", 1083, oid),
2951 => resolve_array("UUID[]", 2950, oid),
1231 => resolve_array("NUMERIC[]", 1700, oid),
1041 => Ok("Vec<String>"), 651 => Ok("Vec<String>"), 1040 => Ok("Vec<String>"), 1187 => Ok("Vec<String>"), _ => {
let name = bsql_core::types::pg_name_for_oid(oid).unwrap_or("unknown");
Err(format!(
"unsupported PostgreSQL type `{name}` (OID {oid}). \
Enable the appropriate feature flag in bsql or cast to a supported type."
))
}
}
}
fn resolve_array(pg_name: &str, element_oid: u32, array_oid: u32) -> Result<&'static str, String> {
match element_oid {
1184 => {
if cfg!(feature = "time") {
Ok("Vec<::time::OffsetDateTime>")
} else if cfg!(feature = "chrono") {
Ok("Vec<::chrono::DateTime<::chrono::Utc>>")
} else {
Err(feature_error(pg_name, array_oid, &["time", "chrono"]))
}
}
1114 => {
if cfg!(feature = "time") {
Ok("Vec<::time::PrimitiveDateTime>")
} else if cfg!(feature = "chrono") {
Ok("Vec<::chrono::NaiveDateTime>")
} else {
Err(feature_error(pg_name, array_oid, &["time", "chrono"]))
}
}
1082 => {
if cfg!(feature = "time") {
Ok("Vec<::time::Date>")
} else if cfg!(feature = "chrono") {
Ok("Vec<::chrono::NaiveDate>")
} else {
Err(feature_error(pg_name, array_oid, &["time", "chrono"]))
}
}
1083 => {
if cfg!(feature = "time") {
Ok("Vec<::time::Time>")
} else if cfg!(feature = "chrono") {
Ok("Vec<::chrono::NaiveTime>")
} else {
Err(feature_error(pg_name, array_oid, &["time", "chrono"]))
}
}
2950 => {
if cfg!(feature = "uuid") {
Ok("Vec<::uuid::Uuid>")
} else {
Err(feature_error(pg_name, array_oid, &["uuid"]))
}
}
1700 => {
if cfg!(feature = "decimal") {
Ok("Vec<::rust_decimal::Decimal>")
} else {
Err(feature_error(pg_name, array_oid, &["decimal"]))
}
}
_ => Err(format!(
"unsupported PostgreSQL array type `{pg_name}` (OID {array_oid})."
)),
}
}
pub fn is_param_compatible_extended(rust_type: &str, pg_oid: u32) -> bool {
if bsql_core::types::is_param_compatible(rust_type, pg_oid) {
return true;
}
match (rust_type, pg_oid) {
("::time::OffsetDateTime" | "time::OffsetDateTime" | "OffsetDateTime", 1184) => {
cfg!(feature = "time")
}
("::time::PrimitiveDateTime" | "time::PrimitiveDateTime" | "PrimitiveDateTime", 1114) => {
cfg!(feature = "time")
}
("::time::Date" | "time::Date", 1082) => cfg!(feature = "time"),
("::time::Time" | "time::Time", 1083) => cfg!(feature = "time"),
(
"::chrono::DateTime<::chrono::Utc>"
| "chrono::DateTime<chrono::Utc>"
| "chrono::DateTime<Utc>",
1184,
) => {
cfg!(feature = "chrono")
}
("::chrono::NaiveDateTime" | "chrono::NaiveDateTime" | "NaiveDateTime", 1114) => {
cfg!(feature = "chrono")
}
("::chrono::NaiveDate" | "chrono::NaiveDate" | "NaiveDate", 1082) => {
cfg!(feature = "chrono")
}
("::chrono::NaiveTime" | "chrono::NaiveTime" | "NaiveTime", 1083) => {
cfg!(feature = "chrono")
}
("::uuid::Uuid" | "uuid::Uuid" | "Uuid", 2950) => cfg!(feature = "uuid"),
("::rust_decimal::Decimal" | "rust_decimal::Decimal" | "Decimal", 1700) => {
cfg!(feature = "decimal")
}
_ => false,
}
}
fn feature_error(pg_type: &str, oid: u32, features: &[&str]) -> String {
let features_str = features
.iter()
.map(|f| format!("\"{f}\""))
.collect::<Vec<_>>()
.join(" or ");
format!("column type is {pg_type} (OID {oid}) — enable feature {features_str} in bsql")
}
pub fn is_known_non_enum_type(rust_type: &str) -> bool {
matches!(
rust_type,
"bool"
| "i16"
| "i32"
| "i64"
| "f32"
| "f64"
| "u32"
| "Vec<u8>"
| "&[u8]"
| "Vec<bool>"
| "Vec<i16>"
| "Vec<i32>"
| "Vec<i64>"
| "Vec<f32>"
| "Vec<f64>"
| "Vec<String>"
| "&[bool]"
| "&[i16]"
| "&[i32]"
| "&[i64]"
| "&[f32]"
| "&[f64]"
| "&[&str]"
| "&[String]"
) || rust_type.starts_with("::time::")
|| rust_type.starts_with("time::")
|| rust_type.starts_with("::chrono::")
|| rust_type.starts_with("chrono::")
|| rust_type.starts_with("::uuid::")
|| rust_type.starts_with("uuid::")
|| rust_type.starts_with("::rust_decimal::")
|| rust_type.starts_with("rust_decimal::")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base_types_resolve() {
assert_eq!(resolve_rust_type(23).unwrap(), "i32");
assert_eq!(resolve_rust_type(25).unwrap(), "String");
assert_eq!(resolve_rust_type(16).unwrap(), "bool");
}
#[test]
fn unknown_oid_errors() {
assert!(resolve_rust_type(99999).is_err());
}
#[cfg(feature = "time")]
#[test]
fn timestamptz_resolves_to_time() {
assert_eq!(resolve_rust_type(1184).unwrap(), "::time::OffsetDateTime");
assert_eq!(
resolve_rust_type(1114).unwrap(),
"::time::PrimitiveDateTime"
);
assert_eq!(resolve_rust_type(1082).unwrap(), "::time::Date");
assert_eq!(resolve_rust_type(1083).unwrap(), "::time::Time");
}
#[cfg(feature = "uuid")]
#[test]
fn uuid_resolves() {
assert_eq!(resolve_rust_type(2950).unwrap(), "::uuid::Uuid");
}
#[cfg(not(any(feature = "time", feature = "chrono")))]
#[test]
fn timestamptz_errors_without_feature() {
let err = resolve_rust_type(1184).unwrap_err();
assert!(err.contains("TIMESTAMPTZ"), "unexpected error: {err}");
assert!(err.contains("time"), "should suggest time feature: {err}");
}
#[cfg(not(feature = "uuid"))]
#[test]
fn uuid_errors_without_feature() {
let err = resolve_rust_type(2950).unwrap_err();
assert!(err.contains("UUID"), "unexpected error: {err}");
assert!(err.contains("uuid"), "should suggest uuid feature: {err}");
}
#[test]
fn base_param_compat_still_works() {
assert!(is_param_compatible_extended("i32", 23));
assert!(is_param_compatible_extended("&str", 25));
assert!(!is_param_compatible_extended("i32", 25));
}
#[cfg(feature = "uuid")]
#[test]
fn uuid_param_compat() {
assert!(is_param_compatible_extended("::uuid::Uuid", 2950));
assert!(is_param_compatible_extended("uuid::Uuid", 2950));
assert!(is_param_compatible_extended("Uuid", 2950));
}
#[cfg(feature = "time")]
#[test]
fn time_param_compat() {
assert!(is_param_compatible_extended("::time::OffsetDateTime", 1184));
assert!(is_param_compatible_extended("time::OffsetDateTime", 1184));
assert!(is_param_compatible_extended("OffsetDateTime", 1184));
assert!(is_param_compatible_extended("::time::Date", 1082));
}
#[cfg(all(feature = "chrono", not(feature = "time")))]
#[test]
fn timestamptz_resolves_to_chrono() {
assert_eq!(
resolve_rust_type(1184).unwrap(),
"::chrono::DateTime<::chrono::Utc>"
);
assert_eq!(resolve_rust_type(1114).unwrap(), "::chrono::NaiveDateTime");
assert_eq!(resolve_rust_type(1082).unwrap(), "::chrono::NaiveDate");
assert_eq!(resolve_rust_type(1083).unwrap(), "::chrono::NaiveTime");
}
#[cfg(feature = "chrono")]
#[test]
fn chrono_param_compat() {
assert!(is_param_compatible_extended("::chrono::NaiveDate", 1082));
assert!(is_param_compatible_extended("chrono::NaiveDate", 1082));
assert!(is_param_compatible_extended("NaiveDate", 1082));
assert!(is_param_compatible_extended(
"::chrono::NaiveDateTime",
1114
));
}
#[cfg(feature = "decimal")]
#[test]
fn numeric_resolves_to_decimal() {
assert_eq!(resolve_rust_type(1700).unwrap(), "::rust_decimal::Decimal");
}
#[cfg(feature = "decimal")]
#[test]
fn decimal_param_compat() {
assert!(is_param_compatible_extended(
"::rust_decimal::Decimal",
1700
));
assert!(is_param_compatible_extended("rust_decimal::Decimal", 1700));
assert!(is_param_compatible_extended("Decimal", 1700));
}
#[cfg(not(feature = "decimal"))]
#[test]
fn numeric_errors_without_feature() {
let err = resolve_rust_type(1700).unwrap_err();
assert!(err.contains("NUMERIC"), "unexpected error: {err}");
assert!(
err.contains("decimal"),
"should suggest decimal feature: {err}"
);
}
#[test]
fn known_non_enum_scalars() {
assert!(is_known_non_enum_type("bool"));
assert!(is_known_non_enum_type("i32"));
assert!(is_known_non_enum_type("i64"));
assert!(is_known_non_enum_type("f64"));
assert!(is_known_non_enum_type("u32"));
}
#[test]
fn known_non_enum_arrays() {
assert!(is_known_non_enum_type("Vec<u8>"));
assert!(is_known_non_enum_type("Vec<i32>"));
assert!(is_known_non_enum_type("&[i64]"));
assert!(is_known_non_enum_type("&[&str]"));
}
#[test]
fn known_non_enum_crate_types() {
assert!(is_known_non_enum_type("::time::OffsetDateTime"));
assert!(is_known_non_enum_type("time::Date"));
assert!(is_known_non_enum_type("::chrono::NaiveDate"));
assert!(is_known_non_enum_type("chrono::NaiveDateTime"));
assert!(is_known_non_enum_type("::uuid::Uuid"));
assert!(is_known_non_enum_type("uuid::Uuid"));
assert!(is_known_non_enum_type("::rust_decimal::Decimal"));
assert!(is_known_non_enum_type("rust_decimal::Decimal"));
}
#[test]
fn str_and_string_not_known_non_enum() {
assert!(!is_known_non_enum_type("&str"));
assert!(!is_known_non_enum_type("String"));
}
#[test]
fn unknown_custom_type_not_known_non_enum() {
assert!(!is_known_non_enum_type("TicketStatus"));
assert!(!is_known_non_enum_type("MyEnum"));
}
#[test]
fn json_resolves_to_string() {
assert_eq!(resolve_rust_type(114).unwrap(), "String");
}
#[test]
fn jsonb_resolves_to_string() {
assert_eq!(resolve_rust_type(3802).unwrap(), "String");
}
#[test]
fn json_array_resolves_to_vec_string() {
assert_eq!(resolve_rust_type(199).unwrap(), "Vec<String>");
}
#[test]
fn jsonb_array_resolves_to_vec_string() {
assert_eq!(resolve_rust_type(3807).unwrap(), "Vec<String>");
}
#[test]
fn interval_resolves_to_string() {
assert_eq!(resolve_rust_type(1186).unwrap(), "String");
}
#[test]
fn inet_resolves_to_string() {
assert_eq!(resolve_rust_type(869).unwrap(), "String");
}
#[test]
fn cidr_resolves_to_string() {
assert_eq!(resolve_rust_type(650).unwrap(), "String");
}
#[test]
fn macaddr_resolves_to_string() {
assert_eq!(resolve_rust_type(829).unwrap(), "String");
}
#[test]
fn json_param_compat() {
assert!(is_param_compatible_extended("&str", 114));
assert!(is_param_compatible_extended("String", 114));
assert!(is_param_compatible_extended("&str", 3802));
assert!(is_param_compatible_extended("String", 3802));
}
#[test]
fn interval_param_compat() {
assert!(is_param_compatible_extended("&str", 1186));
assert!(is_param_compatible_extended("String", 1186));
}
#[test]
fn inet_param_compat() {
assert!(is_param_compatible_extended("&str", 869));
assert!(is_param_compatible_extended("String", 869));
}
#[test]
fn inet_array_resolves() {
assert_eq!(resolve_rust_type(1041).unwrap(), "Vec<String>");
}
#[test]
fn interval_array_resolves() {
assert_eq!(resolve_rust_type(1187).unwrap(), "Vec<String>");
}
#[cfg(feature = "time")]
#[test]
fn timestamp_resolves_to_primitive_datetime() {
assert_eq!(
resolve_rust_type(1114).unwrap(),
"::time::PrimitiveDateTime"
);
}
#[cfg(all(feature = "chrono", not(feature = "time")))]
#[test]
fn timestamp_resolves_to_naive_datetime() {
assert_eq!(resolve_rust_type(1114).unwrap(), "::chrono::NaiveDateTime");
}
#[test]
fn resolve_json_oid_114() {
assert_eq!(resolve_rust_type(114).unwrap(), "String");
}
#[test]
fn resolve_inet_array_oid() {
assert_eq!(resolve_rust_type(1041).unwrap(), "Vec<String>");
}
#[test]
fn resolve_cidr_array_oid() {
assert_eq!(resolve_rust_type(651).unwrap(), "Vec<String>");
}
#[test]
fn resolve_macaddr_array_oid() {
assert_eq!(resolve_rust_type(1040).unwrap(), "Vec<String>");
}
#[test]
fn resolve_interval_array_oid() {
assert_eq!(resolve_rust_type(1187).unwrap(), "Vec<String>");
}
#[test]
fn resolve_json_array_oid() {
assert_eq!(resolve_rust_type(199).unwrap(), "Vec<String>");
}
#[test]
fn resolve_jsonb_array_oid() {
assert_eq!(resolve_rust_type(3807).unwrap(), "Vec<String>");
}
#[test]
fn timetz_oid_unsupported() {
let err = resolve_rust_type(1266).unwrap_err();
assert!(err.contains("TIMETZ"), "should mention TIMETZ: {err}");
}
#[test]
fn resolve_cidr_oid() {
assert_eq!(resolve_rust_type(650).unwrap(), "String");
}
#[test]
fn resolve_macaddr_oid() {
assert_eq!(resolve_rust_type(829).unwrap(), "String");
}
#[test]
fn known_non_enum_byteslice() {
assert!(is_known_non_enum_type("&[u8]"));
}
#[test]
fn param_compat_extended_network_types() {
assert!(is_param_compatible_extended("&str", 869)); assert!(is_param_compatible_extended("String", 650)); assert!(is_param_compatible_extended("&str", 829)); assert!(is_param_compatible_extended("String", 1186)); }
}