Skip to main content

rust_ef_postgres/
row_conversion.rs

1use rust_ef::provider::DbValue;
2use tokio_postgres::types::Type as PgType;
3
4/// Converts a cell from a `tokio_postgres::Row` into a native `DbValue`.
5///
6/// Dispatches on the column's PostgreSQL type OID so that native
7/// `TIMESTAMPTZ`/`TIMESTAMP`/`DATE`/`UUID`/`BOOL`/`INT2`/`INT4`/`INT8`/
8/// `FLOAT4`/`FLOAT8`/`BYTEA` columns are read via their `FromSql` impls
9/// (enabled by `with-chrono-0_4`/`with-uuid-1`) and returned as the
10/// corresponding `DbValue` variant directly — no String round-trip.
11///
12/// NUMERIC / TEXT / VARCHAR / CHAR / JSON / etc. fall back to the `String`
13/// `FromSql` impl (works correctly via the binary protocol).
14pub(crate) fn cell_to_db_value(
15    row: &tokio_postgres::Row,
16    col_idx: usize,
17    pg_type: &PgType,
18) -> DbValue {
19    use tokio_postgres::types::FromSql;
20    match *pg_type {
21        PgType::BOOL => {
22            let opt: Option<bool> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
23            opt.map(DbValue::Bool).unwrap_or(DbValue::Null)
24        }
25        PgType::INT2 => {
26            let opt: Option<i16> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
27            opt.map(DbValue::I16).unwrap_or(DbValue::Null)
28        }
29        PgType::INT4 => {
30            let opt: Option<i32> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
31            opt.map(DbValue::I32).unwrap_or(DbValue::Null)
32        }
33        PgType::INT8 => {
34            let opt: Option<i64> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
35            opt.map(DbValue::I64).unwrap_or(DbValue::Null)
36        }
37        PgType::FLOAT4 => {
38            let opt: Option<f32> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
39            opt.map(DbValue::F32).unwrap_or(DbValue::Null)
40        }
41        PgType::FLOAT8 => {
42            let opt: Option<f64> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
43            opt.map(DbValue::F64).unwrap_or(DbValue::Null)
44        }
45        PgType::BYTEA => {
46            let opt: Option<Vec<u8>> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
47            opt.map(DbValue::Bytes).unwrap_or(DbValue::Null)
48        }
49        PgType::TIMESTAMPTZ => {
50            let opt: Option<chrono::DateTime<chrono::Utc>> =
51                FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
52            opt.map(DbValue::DateTime).unwrap_or(DbValue::Null)
53        }
54        PgType::TIMESTAMP => {
55            let opt: Option<chrono::NaiveDateTime> =
56                FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
57            opt.map(DbValue::NaiveDateTime).unwrap_or(DbValue::Null)
58        }
59        PgType::DATE => {
60            let opt: Option<chrono::NaiveDate> =
61                FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
62            opt.map(DbValue::NaiveDate).unwrap_or(DbValue::Null)
63        }
64        PgType::UUID => {
65            let opt: Option<uuid::Uuid> =
66                FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
67            opt.map(DbValue::Uuid).unwrap_or(DbValue::Null)
68        }
69        // NUMERIC / TEXT / VARCHAR / CHAR / JSON / etc. — String FromSql works
70        // correctly via the binary protocol. NUMERIC returns a string form
71        // that round-trips losslessly through PG's NUMERIC type.
72        _ => {
73            let opt: Option<String> = FromSql::from_sql_nullable(pg_type, row.get(col_idx)).ok();
74            opt.map(DbValue::String).unwrap_or(DbValue::Null)
75        }
76    }
77}