geekorm_core/builder/
joins.rs

1use crate::{Table, ToSqlite};
2
3/// Struct for joining tables
4#[derive(Debug, Clone, Default)]
5pub struct TableJoins {
6    joins: Vec<TableJoin>,
7}
8
9impl Iterator for TableJoins {
10    type Item = TableJoin;
11
12    fn next(&mut self) -> Option<Self::Item> {
13        self.joins.pop()
14    }
15}
16
17impl TableJoins {
18    /// Add a new join to the table
19    pub fn push(&mut self, join: TableJoin) {
20        self.joins.push(join);
21    }
22
23    /// Get the join by name
24    pub fn get(&self, name: &str) -> Option<&TableJoin> {
25        self.joins.iter().find(|join| match join {
26            TableJoin::InnerJoin(opts) => opts.child.name == name,
27        })
28    }
29
30    /// Check if the joins are empty
31    pub fn is_empty(&self) -> bool {
32        self.joins.is_empty()
33    }
34}
35
36impl ToSqlite for TableJoins {
37    fn on_select(&self, query: &crate::QueryBuilder) -> Result<String, crate::Error> {
38        let mut full_query = String::new();
39        for join in self.joins.iter() {
40            let sql = join.on_select(query)?;
41            full_query.push_str(sql.as_str());
42        }
43        Ok(full_query)
44    }
45}
46
47/// Enum for joining tables
48#[derive(Debug, Clone)]
49pub enum TableJoin {
50    /// Inner Join
51    InnerJoin(TableJoinOptions),
52}
53
54impl TableJoin {
55    /// Create a new inner join between two tables
56    pub fn new(parent: Table, child: Table) -> Self {
57        TableJoin::InnerJoin(TableJoinOptions { parent, child })
58    }
59
60    /// Check if a Table.Column is valid
61    pub fn is_valid_column(&self, column: &str) -> bool {
62        match self {
63            TableJoin::InnerJoin(opts) => opts.parent.is_valid_column(column),
64        }
65    }
66}
67
68impl ToSqlite for TableJoin {
69    fn on_select(&self, qb: &crate::QueryBuilder) -> Result<String, crate::Error> {
70        match self {
71            TableJoin::InnerJoin(opts) => Ok(format!(
72                "INNER JOIN {} ON {}",
73                opts.child.name,
74                opts.on_select(qb)?
75            )),
76        }
77    }
78}
79
80/// Struct for Options regarding joining tables together
81///
82/// Parent Table is the left, Child Table is the right
83#[derive(Debug, Clone)]
84pub struct TableJoinOptions {
85    /// Parent Table
86    pub parent: Table,
87    /// Child Table
88    pub child: Table,
89}
90
91impl TableJoinOptions {
92    /// Check if a Table.Column is valid
93    pub fn is_valid_column(&self, column: &str) -> bool {
94        self.parent.is_valid_column(column) || self.child.is_valid_column(column)
95    }
96}
97
98impl ToSqlite for TableJoinOptions {
99    /// Generate the SQL for the join statement
100    fn on_select(&self, _: &crate::QueryBuilder) -> Result<String, crate::Error> {
101        // Get the parent column to join on
102        let pcolumn = self.parent.get_foreign_key(self.child.name.clone());
103        // Get the column name or alias
104        let pcolumn_name = if !pcolumn.alias.is_empty() {
105            pcolumn.alias.clone()
106        } else {
107            pcolumn.name.clone()
108        };
109        // Get the column to join on or use the primary key of the table
110        // TODO(geekmasher): Add support for dynamic column lookup
111        let ccolumn = self.child.get_primary_key();
112
113        Ok(format!(
114            "{ctable}.{ccolumn} = {ptable}.{pcolumn}",
115            ctable = self.child.name,
116            ccolumn = ccolumn,
117            ptable = self.parent.name,
118            pcolumn = pcolumn_name,
119        ))
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use crate::{Column, Columns};
126
127    use super::*;
128
129    fn table_parent(name: String) -> Table {
130        Table {
131            name,
132            database: None,
133            columns: Columns {
134                columns: vec![
135                    Column {
136                        name: String::from("id"),
137                        column_type: crate::ColumnType::Identifier(
138                            crate::ColumnTypeOptions::primary_key(),
139                        ),
140                        ..Default::default()
141                    },
142                    Column {
143                        name: String::from("image_id"),
144                        column_type: crate::ColumnType::ForeignKey(
145                            crate::ColumnTypeOptions::foreign_key(String::from("Child.id")),
146                        ),
147                        ..Default::default()
148                    },
149                ],
150            },
151        }
152    }
153
154    fn table_child(name: String) -> Table {
155        Table {
156            name,
157            database: None,
158            columns: Columns {
159                columns: vec![Column {
160                    name: String::from("id"),
161                    column_type: crate::ColumnType::Identifier(
162                        crate::ColumnTypeOptions::primary_key(),
163                    ),
164                    ..Default::default()
165                }],
166            },
167        }
168    }
169
170    #[test]
171    fn test_table_join_on_select() {
172        let join = TableJoin::InnerJoin(TableJoinOptions {
173            parent: table_parent(String::from("Parent")),
174            child: table_child(String::from("Child")),
175        });
176
177        let select_query = join
178            .on_select(&crate::QueryBuilder::select())
179            .expect("Failed to generate select query");
180        assert_eq!(
181            select_query,
182            "INNER JOIN Child ON Child.id = Parent.image_id"
183        )
184    }
185
186    #[test]
187    fn test_join_options() {
188        let join = TableJoinOptions {
189            parent: table_parent(String::from("Parent")),
190            child: table_child(String::from("Child")),
191        };
192
193        let select_query = join.on_select(&crate::QueryBuilder::select()).unwrap();
194        assert_eq!(select_query, "Child.id = Parent.image_id");
195    }
196}