Skip to main content

ic_dbms_api/dbms/query/
join.rs

1//! Join types for cross-table query operations.
2
3use candid::CandidType;
4use serde::{Deserialize, Serialize};
5
6/// Specifies the JOIN operation type.
7#[derive(Debug, Clone, PartialEq, Eq, CandidType, Serialize, Deserialize)]
8pub enum JoinType {
9    Inner,
10    Left,
11    Right,
12    Full,
13}
14
15/// A JOIN clause specifying which table to join and on which columns.
16#[derive(Debug, Clone, PartialEq, Eq, CandidType, Serialize, Deserialize)]
17pub struct Join {
18    /// The type of join (INNER, LEFT, RIGHT, FULL).
19    pub join_type: JoinType,
20    /// The table to join with.
21    pub table: String,
22    /// Column on the left side of the ON condition.
23    /// Qualified ("users.id") or unqualified ("id", defaults to FROM table).
24    pub left_column: String,
25    /// Column on the right side of the ON condition.
26    /// Qualified ("posts.user") or unqualified ("user", defaults to joined table).
27    pub right_column: String,
28}
29
30impl Join {
31    /// Creates an INNER JOIN.
32    pub fn inner(table: &str, left_column: &str, right_column: &str) -> Self {
33        Self {
34            join_type: JoinType::Inner,
35            table: table.to_string(),
36            left_column: left_column.to_string(),
37            right_column: right_column.to_string(),
38        }
39    }
40
41    /// Creates a LEFT JOIN.
42    pub fn left(table: &str, left_column: &str, right_column: &str) -> Self {
43        Self {
44            join_type: JoinType::Left,
45            table: table.to_string(),
46            left_column: left_column.to_string(),
47            right_column: right_column.to_string(),
48        }
49    }
50
51    /// Creates a RIGHT JOIN.
52    pub fn right(table: &str, left_column: &str, right_column: &str) -> Self {
53        Self {
54            join_type: JoinType::Right,
55            table: table.to_string(),
56            left_column: left_column.to_string(),
57            right_column: right_column.to_string(),
58        }
59    }
60
61    /// Creates a FULL OUTER JOIN.
62    pub fn full(table: &str, left_column: &str, right_column: &str) -> Self {
63        Self {
64            join_type: JoinType::Full,
65            table: table.to_string(),
66            left_column: left_column.to_string(),
67            right_column: right_column.to_string(),
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn test_should_create_inner_join() {
78        let join = Join::inner("posts", "id", "user");
79        assert_eq!(join.join_type, JoinType::Inner);
80        assert_eq!(join.table, "posts");
81        assert_eq!(join.left_column, "id");
82        assert_eq!(join.right_column, "user");
83    }
84
85    #[test]
86    fn test_should_create_left_join() {
87        let join = Join::left("posts", "id", "user");
88        assert_eq!(join.join_type, JoinType::Left);
89    }
90
91    #[test]
92    fn test_should_create_right_join() {
93        let join = Join::right("posts", "id", "user");
94        assert_eq!(join.join_type, JoinType::Right);
95    }
96
97    #[test]
98    fn test_should_create_full_join() {
99        let join = Join::full("posts", "id", "user");
100        assert_eq!(join.join_type, JoinType::Full);
101    }
102
103    #[test]
104    fn test_should_encode_decode_join_candid() {
105        let join = Join::inner("posts", "users.id", "user");
106        let encoded = candid::encode_one(&join).unwrap();
107        let decoded: Join = candid::decode_one(&encoded).unwrap();
108        assert_eq!(join, decoded);
109    }
110
111    #[test]
112    fn test_should_encode_decode_join_type_candid() {
113        for jt in [
114            JoinType::Inner,
115            JoinType::Left,
116            JoinType::Right,
117            JoinType::Full,
118        ] {
119            let encoded = candid::encode_one(&jt).unwrap();
120            let decoded: JoinType = candid::decode_one(&encoded).unwrap();
121            assert_eq!(jt, decoded);
122        }
123    }
124}