use std::cell::RefCell;
use std::rc::Rc;
use anyhow::anyhow;
use postgres::Row;
use thiserror::Error;
use crate::psql::connection::PsqlConnection;
use crate::psql::dto::*;
use lezeh_common::types::ResultAnyError;
pub struct Query {
connection: Rc<RefCell<PsqlConnection>>,
}
#[derive(Error, Debug)]
pub enum QueryError {
#[error("Row with column {column} = {identifier:?} is not found in table {table_id}")]
RowNotFound {
table_id: String,
column: String,
identifier: String,
},
#[error("Too many rows returned({row_count}), expecting only {expected_row_count}")]
TooManyRows {
row_count: usize,
expected_row_count: usize,
},
}
pub struct FetchRowInput<'a> {
pub table_id: &'a PsqlTableIdentity,
pub column_name: &'a str,
pub column_value: &'a PsqlParamValue,
}
impl<'b> FetchRowInput<'b> {
pub fn psql_param_value<'a>(
column_value: String,
column: PsqlTableColumn,
) -> ResultAnyError<PsqlParamValue> {
let data_type: String = column.data_type.to_string();
let mut value: PsqlParamValue = Box::new(column_value.clone());
if data_type == "integer" {
let convert_column_value = column_value.clone().parse::<i32>().map_err(|err| {
return anyhow!(
"Cannot cast column '{}' of value {} to integer. Error: {}",
column.name,
column_value,
err
);
})?;
value = Box::new(convert_column_value);
} else if data_type == "uuid" {
let uuid = Uuid::from_str(&column_value)?;
value = Box::new(uuid);
}
return Ok(value);
}
}
impl Query {
fn find_rows(&mut self, input: &FetchRowInput) -> ResultAnyError<Vec<Row>> {
let query_str = format!(
"SELECT * FROM {} where {} = $1",
input.table_id, input.column_name
);
let mut connection = self.connection.borrow_mut();
let connection = connection.get();
let statement = connection.prepare(&query_str)?;
return connection
.query(&statement, &[input.column_value.as_ref()])
.map_err(anyhow::Error::from);
}
fn find_one_row(&mut self, input: &FetchRowInput) -> ResultAnyError<Option<Row>> {
let rows_result = self.find_rows(input);
return match rows_result {
Err(any) => Err(any),
Ok(mut rows) => {
if rows.len() > 1 {
return Err(anyhow!(QueryError::TooManyRows {
row_count: rows.len(),
expected_row_count: 1,
}));
}
if rows.len() == 0 {
return Ok(None);
}
return Ok(Some(rows.remove(0)));
}
};
}
pub fn get_column_metadata<'a>(
&mut self,
table_id: &PsqlTableIdentity,
column_name: &str,
) -> ResultAnyError<Row> {
let query_str =
"SELECT * FROM information_schema.columns where table_schema = $1 and table_name = $2 and column_name = $3";
let mut connection = self.connection.borrow_mut();
let connection = connection.get();
let statement = connection.prepare(&query_str)?;
return connection
.query_one(
&statement,
&[
&table_id.schema.to_string(),
&table_id.name.to_string(),
&column_name.to_string(),
],
)
.map_err(anyhow::Error::from);
}
}
#[cfg_attr(test, mockall::automock)]
pub trait TableMetadata {
fn get_column(
&self,
table_id: &PsqlTableIdentity,
column_name: &str,
) -> ResultAnyError<PsqlTableColumn>;
fn get_rows<'a>(
&self,
table: PsqlTable,
column_name: &str,
id: &PsqlParamValue,
) -> ResultAnyError<Vec<PsqlTableRow>>;
fn get_one_row(
&self,
table: &PsqlTable,
column_name: &str,
id: &str,
) -> ResultAnyError<PsqlTableRow>;
}
pub struct TableMetadataImpl {
query: RefCell<Query>,
}
impl TableMetadataImpl {
pub fn new(psql_connection: Rc<RefCell<PsqlConnection>>) -> TableMetadataImpl {
return TableMetadataImpl {
query: RefCell::new(Query {
connection: psql_connection,
}),
};
}
}
impl TableMetadata for TableMetadataImpl {
fn get_column(
&self,
table_id: &PsqlTableIdentity,
column_name: &str,
) -> ResultAnyError<PsqlTableColumn> {
let row = self
.query
.borrow_mut()
.get_column_metadata(table_id, column_name)?;
let column = PsqlTableColumn::new(column_name.to_string(), row.get("data_type"));
return Ok(column);
}
fn get_rows(
&self,
table: PsqlTable,
column_name: &str,
id: &PsqlParamValue,
) -> ResultAnyError<Vec<PsqlTableRow>> {
return self
.query
.borrow_mut()
.find_rows(&FetchRowInput {
table_id: &table.id,
column_name,
column_value: id,
})
.and_then(|rows| {
return rows
.into_iter()
.map(|inner_row| {
return PsqlTableRow::new(table.clone(), Rc::new(inner_row));
})
.collect::<ResultAnyError<Vec<PsqlTableRow>>>();
});
}
fn get_one_row<'a>(
&self,
table: &PsqlTable,
column_name: &str,
id: &str,
) -> ResultAnyError<PsqlTableRow> {
let column = self.get_column(&table.id, column_name)?;
let id: PsqlParamValue = FetchRowInput::psql_param_value(id.to_string(), column)?;
let row = self.query.borrow_mut().find_one_row(&FetchRowInput {
table_id: &table.id,
column_name,
column_value: &id,
})?;
return row
.ok_or_else(|| {
anyhow!(QueryError::RowNotFound {
table_id: format!("{:#?}", table.id),
column: column_name.into(),
identifier: format!("{:#?}", id),
})
})
.and_then(|inner_row| PsqlTableRow::new(table.clone(), Rc::new(inner_row)));
}
}