Skip to main content

lezeh_db/psql/
table_metadata.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use anyhow::anyhow;
5use postgres::Row;
6use thiserror::Error;
7
8use crate::psql::connection::PsqlConnection;
9use crate::psql::dto::*;
10use lezeh_common::types::ResultAnyError;
11
12pub struct Query {
13  connection: Rc<RefCell<PsqlConnection>>,
14}
15
16#[derive(Error, Debug)]
17pub enum QueryError {
18  #[error("Row with column {column} = {identifier:?} is not found in table {table_id}")]
19  RowNotFound {
20    table_id: String,
21    column: String,
22    identifier: String,
23  },
24
25  #[error("Too many rows returned({row_count}), expecting only {expected_row_count}")]
26  TooManyRows {
27    row_count: usize,
28    expected_row_count: usize,
29  },
30}
31
32pub struct FetchRowInput<'a> {
33  pub table_id: &'a PsqlTableIdentity,
34  pub column_name: &'a str,
35  pub column_value: &'a PsqlParamValue,
36}
37
38impl<'b> FetchRowInput<'b> {
39  pub fn psql_param_value<'a>(
40    column_value: String,
41    column: PsqlTableColumn,
42  ) -> ResultAnyError<PsqlParamValue> {
43    let data_type: String = column.data_type.to_string();
44    let mut value: PsqlParamValue = Box::new(column_value.clone());
45
46    if data_type == "integer" {
47      let convert_column_value = column_value.clone().parse::<i32>().map_err(|err| {
48        return anyhow!(
49          "Cannot cast column '{}' of value {} to integer. Error: {}",
50          column.name,
51          column_value,
52          err
53        );
54      })?;
55
56      value = Box::new(convert_column_value);
57    } else if data_type == "uuid" {
58      let uuid = Uuid::from_str(&column_value)?;
59
60      value = Box::new(uuid);
61    }
62
63    return Ok(value);
64  }
65}
66
67impl Query {
68  fn find_rows(&mut self, input: &FetchRowInput) -> ResultAnyError<Vec<Row>> {
69    let query_str = format!(
70      "SELECT * FROM {} where {} = $1",
71      input.table_id, input.column_name
72    );
73
74    let mut connection = self.connection.borrow_mut();
75    let connection = connection.get();
76    let statement = connection.prepare(&query_str)?;
77
78    return connection
79      .query(&statement, &[input.column_value.as_ref()])
80      .map_err(anyhow::Error::from);
81  }
82
83  fn find_one_row(&mut self, input: &FetchRowInput) -> ResultAnyError<Option<Row>> {
84    let rows_result = self.find_rows(input);
85
86    return match rows_result {
87      Err(any) => Err(any),
88      Ok(mut rows) => {
89        if rows.len() > 1 {
90          return Err(anyhow!(QueryError::TooManyRows {
91            row_count: rows.len(),
92            expected_row_count: 1,
93          }));
94        }
95
96        if rows.len() == 0 {
97          return Ok(None);
98        }
99
100        return Ok(Some(rows.remove(0)));
101      }
102    };
103  }
104
105  pub fn get_column_metadata<'a>(
106    &mut self,
107    table_id: &PsqlTableIdentity,
108    column_name: &str,
109  ) -> ResultAnyError<Row> {
110    let query_str =
111      "SELECT * FROM information_schema.columns where table_schema = $1 and table_name = $2 and column_name = $3";
112
113    let mut connection = self.connection.borrow_mut();
114    let connection = connection.get();
115    let statement = connection.prepare(&query_str)?;
116
117    return connection
118      .query_one(
119        &statement,
120        &[
121          &table_id.schema.to_string(),
122          &table_id.name.to_string(),
123          &column_name.to_string(),
124        ],
125      )
126      .map_err(anyhow::Error::from);
127  }
128}
129
130#[cfg_attr(test, mockall::automock)]
131pub trait TableMetadata {
132  fn get_column(
133    &self,
134    table_id: &PsqlTableIdentity,
135    column_name: &str,
136  ) -> ResultAnyError<PsqlTableColumn>;
137
138  fn get_rows<'a>(
139    &self,
140    table: PsqlTable,
141    column_name: &str,
142    id: &PsqlParamValue,
143  ) -> ResultAnyError<Vec<PsqlTableRow>>;
144
145  fn get_one_row(
146    &self,
147    table: &PsqlTable,
148    column_name: &str,
149    id: &str,
150  ) -> ResultAnyError<PsqlTableRow>;
151}
152
153pub struct TableMetadataImpl {
154  /// We know that we own this query so it's ok
155  /// to directl borrow_mut() without checking ownership
156  query: RefCell<Query>,
157}
158
159impl TableMetadataImpl {
160  pub fn new(psql_connection: Rc<RefCell<PsqlConnection>>) -> TableMetadataImpl {
161    return TableMetadataImpl {
162      query: RefCell::new(Query {
163        connection: psql_connection,
164      }),
165    };
166  }
167}
168
169impl TableMetadata for TableMetadataImpl {
170  fn get_column(
171    &self,
172    table_id: &PsqlTableIdentity,
173    column_name: &str,
174  ) -> ResultAnyError<PsqlTableColumn> {
175    let row = self
176      .query
177      .borrow_mut()
178      .get_column_metadata(table_id, column_name)?;
179
180    let column = PsqlTableColumn::new(column_name.to_string(), row.get("data_type"));
181
182    return Ok(column);
183  }
184
185  fn get_rows(
186    &self,
187    table: PsqlTable,
188    column_name: &str,
189    id: &PsqlParamValue,
190  ) -> ResultAnyError<Vec<PsqlTableRow>> {
191    return self
192      .query
193      .borrow_mut()
194      .find_rows(&FetchRowInput {
195        table_id: &table.id,
196        column_name,
197        column_value: id,
198      })
199      .and_then(|rows| {
200        return rows
201          .into_iter()
202          .map(|inner_row| {
203            return PsqlTableRow::new(table.clone(), Rc::new(inner_row));
204          })
205          .collect::<ResultAnyError<Vec<PsqlTableRow>>>();
206      });
207  }
208
209  fn get_one_row<'a>(
210    &self,
211    table: &PsqlTable,
212    column_name: &str,
213    id: &str,
214  ) -> ResultAnyError<PsqlTableRow> {
215    let column = self.get_column(&table.id, column_name)?;
216    let id: PsqlParamValue = FetchRowInput::psql_param_value(id.to_string(), column)?;
217
218    let row = self.query.borrow_mut().find_one_row(&FetchRowInput {
219      table_id: &table.id,
220      column_name,
221      column_value: &id,
222    })?;
223
224    return row
225      .ok_or_else(|| {
226        anyhow!(QueryError::RowNotFound {
227          table_id: format!("{:#?}", table.id),
228          column: column_name.into(),
229          identifier: format!("{:#?}", id),
230        })
231      })
232      .and_then(|inner_row| PsqlTableRow::new(table.clone(), Rc::new(inner_row)));
233  }
234}