Skip to main content

karbon_framework/db/
query.rs

1use super::PaginationParams;
2use super::placeholder;
3
4/// Simple SQL query builder for common patterns
5///
6/// # Example
7/// ```ignore
8/// let (sql, count_sql) = QueryBuilder::select("article")
9///     .columns("a.id, a.title, a.status, a.created")
10///     .alias("a")
11///     .join("LEFT JOIN user u ON a.user_id = u.id")
12///     .where_clause("a.status = 'published'")
13///     .search_columns(&["a.title", "a.content"])
14///     .paginate(&params);
15/// ```
16pub struct QueryBuilder {
17    table: String,
18    alias: String,
19    columns: String,
20    joins: Vec<String>,
21    conditions: Vec<String>,
22    search_cols: Vec<String>,
23    group_by: Option<String>,
24}
25
26impl QueryBuilder {
27    pub fn select(table: &str) -> Self {
28        Self {
29            table: table.to_string(),
30            alias: String::new(),
31            columns: "*".to_string(),
32            joins: Vec::new(),
33            conditions: Vec::new(),
34            search_cols: Vec::new(),
35            group_by: None,
36        }
37    }
38
39    pub fn columns(mut self, cols: &str) -> Self {
40        self.columns = cols.to_string();
41        self
42    }
43
44    pub fn alias(mut self, alias: &str) -> Self {
45        self.alias = alias.to_string();
46        self
47    }
48
49    pub fn join(mut self, join: &str) -> Self {
50        self.joins.push(join.to_string());
51        self
52    }
53
54    pub fn where_clause(mut self, condition: &str) -> Self {
55        self.conditions.push(condition.to_string());
56        self
57    }
58
59    pub fn where_if(mut self, condition: &str, apply: bool) -> Self {
60        if apply {
61            self.conditions.push(condition.to_string());
62        }
63        self
64    }
65
66    pub fn search_columns(mut self, cols: &[&str]) -> Self {
67        self.search_cols = cols.iter().map(|s| s.to_string()).collect();
68        self
69    }
70
71    pub fn group_by(mut self, clause: &str) -> Self {
72        self.group_by = Some(clause.to_string());
73        self
74    }
75
76    /// Build SELECT + COUNT queries for pagination
77    ///
78    /// Returns (data_query, count_query)
79    /// Search params are bound separately by the caller
80    pub fn paginate(&self, params: &PaginationParams) -> (String, String) {
81        let from = if self.alias.is_empty() {
82            self.table.clone()
83        } else {
84            format!("{} {}", self.table, self.alias)
85        };
86
87        let joins = self.joins.join(" ");
88
89        let mut where_parts = self.conditions.clone();
90
91        // Add search condition (placeholders start at 1 since WHERE conditions are raw)
92        if params.search.is_some() && !self.search_cols.is_empty() {
93            let search_cond = self
94                .search_cols
95                .iter()
96                .enumerate()
97                .map(|(i, c)| format!("{} LIKE {}", c, placeholder(i + 1)))
98                .collect::<Vec<_>>()
99                .join(" OR ");
100            where_parts.push(format!("({})", search_cond));
101        }
102
103        let where_clause = if where_parts.is_empty() {
104            String::new()
105        } else {
106            format!(" WHERE {}", where_parts.join(" AND "))
107        };
108
109        let group = self
110            .group_by
111            .as_ref()
112            .map(|g| format!(" GROUP BY {}", g))
113            .unwrap_or_default();
114
115        let sort = params.sort_column(&[], "id");
116        let order = params.order_direction();
117
118        let data_query = format!(
119            "SELECT {} FROM {} {}{}{} ORDER BY {} {} LIMIT {} OFFSET {}",
120            self.columns,
121            from,
122            joins,
123            where_clause,
124            group,
125            sort,
126            order,
127            params.safe_per_page(),
128            params.offset()
129        );
130
131        let count_query = format!(
132            "SELECT COUNT(*) as count FROM {} {}{}{}",
133            from, joins, where_clause, group
134        );
135
136        (data_query, count_query)
137    }
138}