Skip to main content

cufflink_pgcodec/
render.rs

1//! A statement's rows as a guest receives them.
2
3use crate::row_json::{column_to_json, column_to_json_v2};
4use cufflink_types::db::{Column, DbError, DbResponse, RowMap};
5use serde_json::Value;
6use tokio_postgres::Row;
7
8/// The rows of a legacy query: an object per row, each column under its
9/// name in the legacy encoding.
10pub fn legacy_rows(rows: &[Row]) -> Vec<Value> {
11    rows.iter()
12        .map(|row| {
13            Value::Object(
14                row.columns()
15                    .iter()
16                    .enumerate()
17                    .map(|(i, column)| {
18                        (
19                            column.name().to_string(),
20                            column_to_json(row, i, column.type_()),
21                        )
22                    })
23                    .collect(),
24            )
25        })
26        .collect()
27}
28
29/// The rows of a `db_call` query with the columns its statement reports;
30/// a value the exact encoding cannot carry is the error.
31pub fn rows_v2(columns: &[tokio_postgres::Column], rows: &[Row]) -> Result<DbResponse, DbError> {
32    let rows = rows
33        .iter()
34        .map(|row| {
35            columns
36                .iter()
37                .enumerate()
38                .map(|(i, column)| {
39                    column_to_json_v2(row, i, column.type_())
40                        .map(|value| (column.name().to_string(), value))
41                })
42                .collect::<Result<RowMap, DbError>>()
43        })
44        .collect::<Result<Vec<_>, _>>()?;
45    Ok(DbResponse::Rows {
46        rows,
47        columns: columns_v2(columns),
48    })
49}
50
51/// The order a statement's columns render in when each row becomes an
52/// object: by name, as a JSON map orders them, and of two columns with the
53/// same name the last wins.
54pub struct ColumnOrder(Vec<(String, usize)>);
55
56impl ColumnOrder {
57    pub fn of(columns: &[tokio_postgres::Column]) -> Self {
58        let last: std::collections::BTreeMap<&str, usize> = columns
59            .iter()
60            .enumerate()
61            .map(|(i, column)| (column.name(), i))
62            .collect();
63        Self(
64            last.into_iter()
65                .map(|(name, i)| (Value::from(name).to_string(), i))
66                .collect(),
67        )
68    }
69
70    /// One row of a legacy query, written as `serde_json::to_string` writes
71    /// the row's object in `legacy_rows`.
72    pub fn legacy_row(&self, row: &Row, out: &mut String) {
73        self.write(out, |i| column_to_json(row, i, row.columns()[i].type_()));
74    }
75
76    /// One row of a `db_call` query, written as the row's map is written in
77    /// the `rows` of a `DbResponse::Rows`.
78    pub fn row_v2(&self, row: &Row, out: &mut String) -> Result<(), DbError> {
79        let mut values = row
80            .columns()
81            .iter()
82            .enumerate()
83            .map(|(i, column)| column_to_json_v2(row, i, column.type_()).map(Some))
84            .collect::<Result<Vec<Option<Value>>, DbError>>()?;
85        self.write(out, |i| values[i].take().unwrap_or(Value::Null));
86        Ok(())
87    }
88
89    fn write(&self, out: &mut String, mut value: impl FnMut(usize) -> Value) {
90        out.push('{');
91        for (n, (key, i)) in self.0.iter().enumerate() {
92            if n > 0 {
93                out.push(',');
94            }
95            out.push_str(key);
96            out.push(':');
97            out.push_str(&value(*i).to_string());
98        }
99        out.push('}');
100    }
101}
102
103/// The columns of a `db_call` query as its `DbResponse::Rows` writes them.
104pub fn columns_v2(columns: &[tokio_postgres::Column]) -> Vec<Column> {
105    columns
106        .iter()
107        .map(|column| Column {
108            name: column.name().to_string(),
109            type_name: column.type_().name().to_string(),
110        })
111        .collect()
112}