use crate::api_request::QualifiedIdentifier;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Relationship {
ForeignKey {
table: QualifiedIdentifier,
foreign_table: QualifiedIdentifier,
is_self: bool,
cardinality: Cardinality,
table_is_view: bool,
foreign_table_is_view: bool,
constraint_name: String,
},
Computed {
function: QualifiedIdentifier,
table: QualifiedIdentifier,
foreign_table: QualifiedIdentifier,
table_alias: QualifiedIdentifier,
to_one: bool,
is_self: bool,
#[serde(default)]
row_argument: Option<String>,
},
}
impl Relationship {
pub fn foreign_table(&self) -> &QualifiedIdentifier {
match self {
Self::ForeignKey { foreign_table, .. } => foreign_table,
Self::Computed { foreign_table, .. } => foreign_table,
}
}
pub fn is_to_one(&self) -> bool {
match self {
Self::ForeignKey { cardinality, .. } => matches!(
cardinality,
Cardinality::M2O { .. } | Cardinality::O2O { .. }
),
Self::Computed { to_one, .. } => *to_one,
}
}
pub fn constraint_name(&self) -> &str {
match self {
Self::ForeignKey { cardinality, .. } => match cardinality {
Cardinality::O2M { constraint, .. }
| Cardinality::M2O { constraint, .. }
| Cardinality::O2O { constraint, .. } => constraint,
Cardinality::M2M(_) => "",
},
Self::Computed { .. } => "",
}
}
pub fn cardinality_name(&self) -> &'static str {
match self {
Self::ForeignKey { cardinality, .. } => match cardinality {
Cardinality::O2M { .. } => "one-to-many",
Cardinality::M2O { .. } => "many-to-one",
Cardinality::O2O { .. } => "one-to-one",
Cardinality::M2M(_) => "many-to-many",
},
Self::Computed { to_one, .. } => match to_one {
true => "many-to-one",
false => "one-to-many",
},
}
}
pub fn describe(&self) -> String {
match self {
Self::ForeignKey {
cardinality: Cardinality::M2M(junction),
..
} => {
let joined = |columns: Vec<String>| columns.join(", ");
format!(
"{} using {}({}) and {}({})",
junction.table.name,
junction.constraint1,
joined(
junction
.source_columns
.iter()
.map(|(_, through)| through.clone())
.collect()
),
junction.constraint2,
joined(
junction
.target_columns
.iter()
.map(|(through, _)| through.clone())
.collect()
),
)
}
Self::ForeignKey {
table,
foreign_table,
cardinality,
..
} => {
let columns = cardinality.columns();
let local = columns
.iter()
.map(|(local, _)| local.clone())
.collect::<Vec<_>>()
.join(", ");
let foreign = columns
.iter()
.map(|(_, foreign)| foreign.clone())
.collect::<Vec<_>>()
.join(", ");
format!(
"{} using {}({}) and {}({})",
self.constraint_name(),
table.name,
local,
foreign_table.name,
foreign
)
}
Self::Computed { function, .. } => function.name.clone(),
}
}
pub fn disambiguator(&self) -> String {
match self {
Self::ForeignKey {
cardinality: Cardinality::M2M(junction),
..
} => junction.table.name.clone(),
Self::ForeignKey {
constraint_name, ..
} => constraint_name.clone(),
Self::Computed { function, .. } => function.name.clone(),
}
}
pub fn matches_hint(&self, hint: &str) -> bool {
let self_referential = matches!(self, Self::ForeignKey { is_self, .. } if *is_self);
let columns_match = self
.join_columns()
.iter()
.any(|(local, foreign)| foreign == hint || (!self_referential && local == hint));
self.constraint_name() == hint
|| self.foreign_table().name == hint
|| columns_match
|| match self {
Self::Computed { function, .. } => function.name == hint,
Self::ForeignKey {
cardinality: Cardinality::M2M(junction),
..
} => {
junction.table.name == hint
|| junction.constraint1 == hint
|| junction.constraint2 == hint
|| junction
.source_columns
.iter()
.chain(junction.target_columns.iter())
.any(|(a, b)| a == hint || b == hint)
}
Self::ForeignKey { .. } => false,
}
}
pub fn is_self_referential(&self) -> bool {
matches!(self, Self::ForeignKey { is_self, .. } if *is_self)
}
pub fn is_one_to_many(&self) -> bool {
matches!(
self,
Self::ForeignKey {
cardinality: Cardinality::O2M { .. },
..
}
)
}
pub fn single_local_column(&self) -> Option<&str> {
match self.join_columns().len() {
1 => match self {
Self::ForeignKey { cardinality, .. } => match cardinality {
Cardinality::O2M { columns, .. }
| Cardinality::M2O { columns, .. }
| Cardinality::O2O { columns, .. } => Some(columns[0].0.as_str()),
Cardinality::M2M(_) => None,
},
Self::Computed { .. } => None,
},
_ => None,
}
}
pub fn single_foreign_column(&self) -> Option<&str> {
match self {
Self::ForeignKey { cardinality, .. } => match cardinality {
Cardinality::O2M { columns, .. }
| Cardinality::M2O { columns, .. }
| Cardinality::O2O { columns, .. }
if columns.len() == 1 =>
{
Some(columns[0].1.as_str())
}
_ => None,
},
Self::Computed { .. } => None,
}
}
pub fn join_columns(&self) -> Vec<(String, String)> {
match self {
Self::ForeignKey { cardinality, .. } => cardinality.columns(),
Self::Computed { .. } => vec![],
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Cardinality {
O2M {
constraint: String,
columns: Vec<(String, String)>,
},
M2O {
constraint: String,
columns: Vec<(String, String)>,
},
O2O {
constraint: String,
columns: Vec<(String, String)>,
is_parent: bool,
},
M2M(Junction),
}
impl Cardinality {
pub fn columns(&self) -> Vec<(String, String)> {
match self {
Self::O2M { columns, .. } => columns.clone(),
Self::M2O { columns, .. } => columns.clone(),
Self::O2O { columns, .. } => columns.clone(),
Self::M2M(junction) => junction.source_columns(),
}
}
pub fn constraint_name(&self) -> &str {
match self {
Self::O2M { constraint, .. } => constraint,
Self::M2O { constraint, .. } => constraint,
Self::O2O { constraint, .. } => constraint,
Self::M2M(junction) => &junction.constraint1,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Junction {
pub table: QualifiedIdentifier,
pub constraint1: String,
pub constraint2: String,
pub source_columns: Vec<(String, String)>,
pub target_columns: Vec<(String, String)>,
}
impl Junction {
pub fn source_columns(&self) -> Vec<(String, String)> {
self.source_columns.clone()
}
pub fn target_columns(&self) -> Vec<(String, String)> {
self.target_columns.clone()
}
}
pub type RelationshipsMap = HashMap<(QualifiedIdentifier, String), Vec<Relationship>>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relationship_foreign_table() {
let rel = Relationship::ForeignKey {
table: QualifiedIdentifier::new("public", "orders"),
foreign_table: QualifiedIdentifier::new("public", "users"),
is_self: false,
cardinality: Cardinality::M2O {
constraint: "orders_user_id_fkey".into(),
columns: vec![("user_id".into(), "id".into())],
},
table_is_view: false,
foreign_table_is_view: false,
constraint_name: "orders_user_id_fkey".into(),
};
assert_eq!(rel.foreign_table().name, "users");
assert!(rel.is_to_one());
}
#[test]
fn test_cardinality_columns() {
let card = Cardinality::O2M {
constraint: "users_id_fkey".into(),
columns: vec![("id".into(), "user_id".into())],
};
let cols = card.columns();
assert_eq!(cols.len(), 1);
assert_eq!(cols[0], ("id".into(), "user_id".into()));
}
}
#[derive(Clone, Debug)]
pub struct MediaHandler {
pub aggregate: QualifiedIdentifier,
pub table: Option<QualifiedIdentifier>,
pub base_type: String,
}
pub type MediaHandlerMap = std::collections::HashMap<(String, String), Vec<MediaHandler>>;