use canyon_crud::{bounds::RowOperations, crud::Transaction, DatabaseType, DatasourceConfig};
use std::collections::HashMap;
use std::fs;
use walkdir::WalkDir;
use crate::{constants, QUERIES_TO_EXECUTE};
#[derive(Debug)]
pub struct CanyonMemory {
pub memory: HashMap<String, String>,
pub renamed_entities: HashMap<String, String>,
}
impl Transaction<Self> for CanyonMemory {}
impl CanyonMemory {
#[allow(clippy::nonminimal_bool)]
pub async fn remember(datasource: &DatasourceConfig<'static>) -> Self {
Self::create_memory(datasource.name, &datasource.properties.db_type).await;
let res = Self::query("SELECT * FROM canyon_memory", [], datasource.name)
.await
.expect("Error querying Canyon Memory");
let mem_results = res.as_canyon_rows();
let mut db_rows = Vec::new();
for row in mem_results.iter() {
let db_row = CanyonMemoryRow {
id: row.get::<i32>("id"),
filepath: row.get::<&str>("filepath"),
struct_name: row.get::<&str>("struct_name"),
};
db_rows.push(db_row);
}
let mut mem = Self {
memory: HashMap::new(),
renamed_entities: HashMap::new(),
};
Self::find_canyon_entity_annotated_structs(&mut mem).await;
let mut values_to_insert = String::new();
let mut updates = Vec::new();
for (filepath, struct_name) in &mem.memory {
let already_in_db = db_rows.iter().any(|el| {
(el.filepath == *filepath && el.struct_name == *struct_name)
|| ((el.filepath != *filepath && el.struct_name == *struct_name)
|| (el.filepath == *filepath && el.struct_name != *struct_name))
});
if !already_in_db {
values_to_insert.push_str(format!("('{filepath}', '{struct_name}'),").as_str());
}
let need_to_update = db_rows.iter().find(|el| {
(el.filepath == *filepath || el.struct_name == *struct_name)
&& !(el.filepath == *filepath && el.struct_name == *struct_name)
});
if let Some(old) = need_to_update {
updates.push(old.struct_name);
let stmt = format!(
"UPDATE canyon_memory SET filepath = '{}', struct_name = '{}' \
WHERE id = {}",
filepath, struct_name, old.id
);
if QUERIES_TO_EXECUTE
.lock()
.unwrap()
.contains_key(datasource.name)
{
QUERIES_TO_EXECUTE
.lock()
.unwrap()
.get_mut(datasource.name)
.unwrap()
.push(stmt);
} else {
QUERIES_TO_EXECUTE
.lock()
.unwrap()
.insert(datasource.name, vec![stmt]);
}
let rename_table = old.struct_name != struct_name;
if rename_table {
mem.renamed_entities.insert(
struct_name.to_lowercase(), old.struct_name.to_lowercase(), );
}
}
}
if !values_to_insert.is_empty() {
values_to_insert.pop();
values_to_insert.push(';');
let stmt = format!(
"INSERT INTO canyon_memory (filepath, struct_name) VALUES {values_to_insert}"
);
if QUERIES_TO_EXECUTE
.lock()
.unwrap()
.contains_key(datasource.name)
{
QUERIES_TO_EXECUTE
.lock()
.unwrap()
.get_mut(datasource.name)
.unwrap()
.push(stmt);
} else {
QUERIES_TO_EXECUTE
.lock()
.unwrap()
.insert(datasource.name, vec![stmt]);
}
}
let in_memory = mem.memory.values().collect::<Vec<&String>>();
db_rows.into_iter().for_each(|db_row| {
if !in_memory.contains(&&db_row.struct_name.to_string())
&& !updates.contains(&db_row.struct_name)
{
let stmt = format!(
"DELETE FROM canyon_memory WHERE struct_name = '{}'",
db_row.struct_name
);
if QUERIES_TO_EXECUTE
.lock()
.unwrap()
.contains_key(datasource.name)
{
QUERIES_TO_EXECUTE
.lock()
.unwrap()
.get_mut(datasource.name)
.unwrap()
.push(stmt);
} else {
QUERIES_TO_EXECUTE
.lock()
.unwrap()
.insert(datasource.name, vec![stmt]);
}
}
});
mem
}
async fn find_canyon_entity_annotated_structs(&mut self) {
for file in WalkDir::new("./src")
.into_iter()
.filter_map(|file| file.ok())
{
if file.metadata().unwrap().is_file()
&& file.path().display().to_string().ends_with(".rs")
{
let contents =
fs::read_to_string(file.path()).expect("Something went wrong reading the file");
let mut canyon_entity_macro_counter = 0;
let mut struct_name = String::new();
for line in contents.split('\n') {
if !line.starts_with("//") && line.contains("struct") {
struct_name.push_str(
line.split_whitespace()
.collect::<Vec<&str>>()
.get(2)
.unwrap_or(&"FAILED"),
)
}
if line.contains("#[") && line.contains("canyon_entity")
&& !line.starts_with("//")
{
canyon_entity_macro_counter += 1;
}
}
match canyon_entity_macro_counter {
0 => (),
1 => {
self.memory.insert(
file.path().display().to_string().replace('\\', "/"),
struct_name,
);
}
_ => panic!(
"Canyon does not support having multiple structs annotated
with `#[canyon::entity]` on the same file when the `#[canyon]`
macro it's present on the program"
),
}
}
}
}
async fn create_memory(datasource_name: &str, database_type: &DatabaseType) {
let query = if database_type == &DatabaseType::PostgreSql {
constants::postgresql_queries::CANYON_MEMORY_TABLE
} else {
constants::mssql_queries::CANYON_MEMORY_TABLE
};
Self::query(query, [], datasource_name)
.await
.expect("Error creating the 'canyon_memory' table");
}
}
#[derive(Debug)]
struct CanyonMemoryRow<'a> {
id: i32,
filepath: &'a str,
struct_name: &'a str,
}