use std::collections::HashMap;
use super::linked_resource::PsqlType as LinkedPsqlType;
use super::simple_resource::PsqlType as SimplePsqlType;
use super::{get_psql_client, ArrErr, PsqlFieldType};
use crate::grpc::server::*;
use crate::resources::{
base::FieldDefinition,
base::{Resource, ResourceObject},
};
pub async fn create_db() -> Result<(), ArrErr> {
psql_info!("(create_db) Creating database tables.");
ResourceObject::<group::Data>::init_table().await?;
ResourceObject::<user::Data>::init_table().await?;
ResourceObject::<user_group::Data>::init_table().await?;
ResourceObject::<vertiport::Data>::init_table().await?;
ResourceObject::<vertiport_group::Data>::init_table().await?;
ResourceObject::<vertipad::Data>::init_table().await?;
ResourceObject::<vertipad_group::Data>::init_table().await?;
ResourceObject::<vehicle::Data>::init_table().await?;
ResourceObject::<vehicle_group::Data>::init_table().await?;
ResourceObject::<pilot::Data>::init_table().await?;
ResourceObject::<adsb::Data>::init_table().await?;
ResourceObject::<flight_plan::Data>::init_table().await?;
ResourceObject::<itinerary::Data>::init_table().await?;
ResourceObject::<itinerary_flight_plan::Data>::init_table().await?;
ResourceObject::<parcel::Data>::init_table().await?;
ResourceObject::<flight_plan_parcel::Data>::init_table().await?;
ResourceObject::<scanner::Data>::init_table().await?;
ResourceObject::<parcel_scan::Data>::init_table().await?;
Ok(())
}
pub async fn drop_db() -> Result<(), ArrErr> {
psql_warn!("(drop_db) Dropping database tables.");
ResourceObject::<parcel_scan::Data>::drop_table().await?;
ResourceObject::<scanner::Data>::drop_table().await?;
ResourceObject::<flight_plan_parcel::Data>::drop_table().await?;
ResourceObject::<parcel::Data>::drop_table().await?;
ResourceObject::<itinerary_flight_plan::Data>::drop_table().await?;
ResourceObject::<itinerary::Data>::drop_table().await?;
ResourceObject::<flight_plan::Data>::drop_table().await?;
ResourceObject::<adsb::Data>::drop_table().await?;
ResourceObject::<pilot::Data>::drop_table().await?;
ResourceObject::<vehicle_group::Data>::drop_table().await?;
ResourceObject::<vehicle::Data>::drop_table().await?;
ResourceObject::<vertipad_group::Data>::drop_table().await?;
ResourceObject::<vertipad::Data>::drop_table().await?;
ResourceObject::<vertiport_group::Data>::drop_table().await?;
ResourceObject::<vertiport::Data>::drop_table().await?;
ResourceObject::<user_group::Data>::drop_table().await?;
ResourceObject::<user::Data>::drop_table().await?;
ResourceObject::<group::Data>::drop_table().await?;
Ok(())
}
pub async fn recreate_db() -> Result<(), ArrErr> {
psql_warn!("(recreate_db) Re-creating database tables.");
drop_db().await?;
create_db().await?;
psql_debug!("(recreate_db) Clearing caches.");
let pool = super::pool::DB_POOL
.get()
.ok_or(ArrErr::Error(String::from(
"(recreate_db) Could not get pool.",
)))?;
pool.manager().statement_caches.clear();
Ok(())
}
#[tonic::async_trait]
pub trait PsqlInitResource
where
Self: Resource + Clone,
{
async fn _init_table_indices() -> Result<(), ArrErr> {
let queries = Self::get_table_indices();
if queries.is_empty() {
return Ok(());
}
let mut client = get_psql_client().await?;
let transaction = client.transaction().await?;
for index_query in queries {
psql_debug!("(_init_table_indices) [{}].", index_query);
if let Err(e) = transaction.execute(&index_query, &[]).await {
psql_error!(
"(_init_table_indices) Failed to create indices for table [{}]: {}",
Self::get_psql_table(),
e
);
return transaction.rollback().await.map_err(ArrErr::from);
}
}
transaction.commit().await.map_err(ArrErr::from)
}
async fn init_table() -> Result<(), ArrErr> {
let mut client = get_psql_client().await?;
let transaction = client.transaction().await?;
let create_table = Self::_get_create_table_query();
psql_debug!("(init_table) [{}].", create_table);
if let Err(e) = transaction.execute(&create_table, &[]).await {
psql_error!("(init_table) Failed to create table: {}", e);
return transaction.rollback().await.map_err(ArrErr::from);
}
transaction.commit().await?;
Self::_init_table_indices().await
}
async fn drop_table() -> Result<(), ArrErr> {
let definition = Self::get_definition();
let mut client = get_psql_client().await?;
let transaction = client.transaction().await?;
let drop_query = format!(r#"DROP TABLE IF EXISTS "{}""#, definition.psql_table);
psql_debug!("(drop_table) [{}].", drop_query);
psql_info!("(drop_table) Dropping table [{}].", definition.psql_table);
if let Err(e) = transaction.execute(&drop_query, &[]).await {
psql_error!(
"(drop_table) Failed to drop table [{}]: {}",
e,
definition.psql_table
);
return transaction.rollback().await.map_err(ArrErr::from);
}
transaction.commit().await.map_err(ArrErr::from)
}
fn _get_create_table_query() -> String;
}
#[tonic::async_trait]
pub trait PsqlInitSimpleResource
where
Self: SimplePsqlType + Clone,
{
fn _get_create_table_query() -> String {
let definition = Self::get_definition();
psql_info!(
"(_get_create_table_query) Composing create table query for [{}].",
definition.psql_table
);
let id_field = match Self::try_get_id_field() {
Ok(field) => field,
Err(e) => {
panic!(
"(_get_create_table_query) Can't convert Object into ResourceObject<Data>: {e}"
)
}
};
let mut fields = vec![];
fields.push(format!(
r#""{}" UUID DEFAULT uuid_generate_v4() PRIMARY KEY"#,
id_field
));
fields.append(&mut get_create_table_fields_sql(&definition.fields));
format!(
r#"CREATE TABLE IF NOT EXISTS "{}" ({})"#,
definition.psql_table,
fields.join(", ")
)
}
}
#[tonic::async_trait]
pub trait PsqlInitLinkedResource
where
Self: LinkedPsqlType + Clone,
{
fn _get_create_table_query() -> String {
let definition = Self::get_definition();
psql_info!(
"(_get_create_table_query) Composing create table query for [{}].",
definition.psql_table
);
let mut fields = vec![];
let mut ids = vec![];
for id in definition.psql_id_cols {
fields.push(format!(r#""{}" UUID NOT NULL"#, id));
ids.push(format!(r#""{}""#, id))
}
fields.append(&mut get_create_table_fields_sql(&definition.fields));
format!(
r#"CREATE TABLE IF NOT EXISTS "{}" ({}, PRIMARY KEY({}) )"#,
definition.psql_table,
fields.join(", "),
ids.join(", ")
)
}
}
fn get_create_table_fields_sql(fields: &HashMap<String, FieldDefinition>) -> Vec<String> {
let mut result: Vec<String> = vec![];
for (key, field) in fields {
let mut field_sql = format!(r#""{}""#, key);
match field.field_type {
PsqlFieldType::TIMESTAMPTZ => field_sql.push_str(" TIMESTAMP WITH TIME ZONE"),
PsqlFieldType::ANYENUM => field_sql.push_str(" TEXT"),
PsqlFieldType::INT2 => field_sql.push_str(" SMALLINT"),
PsqlFieldType::INT4 => field_sql.push_str(" INTEGER"),
PsqlFieldType::INT8 => field_sql.push_str(" BIGINT"),
PsqlFieldType::NUMERIC => field_sql.push_str(" DOUBLE PRECISION"),
PsqlFieldType::BYTEA => field_sql.push_str(" BYTEA"),
PsqlFieldType::PATH => field_sql.push_str(" GEOMETRY"),
PsqlFieldType::POINT => field_sql.push_str(" GEOMETRY"),
PsqlFieldType::POLYGON => field_sql.push_str(" GEOMETRY"),
_ => field_sql.push_str(&format!(" {}", field.field_type.name().to_uppercase())),
}
if field.has_default() {
field_sql.push_str(&format!(" DEFAULT {}", field.get_default()));
}
if field.is_mandatory() {
field_sql.push_str(" NOT NULL");
}
result.push(field_sql);
}
result
}