reinhardt_db/hybrid/expression.rs
1//! SQL expression builders
2
3use serde::{Deserialize, Serialize};
4
5/// Represents a SQL expression
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct SqlExpression {
8 /// The sql.
9 pub sql: String,
10}
11
12impl SqlExpression {
13 /// Creates a new SQL expression from a string
14 ///
15 /// # Safety
16 ///
17 /// This method embeds the `sql` string directly into generated SQL without
18 /// any escaping or parameterization. The caller **must** ensure that the
19 /// input is trusted and not derived from user-controlled data.
20 ///
21 /// # SQL Injection Risk
22 ///
23 /// Passing unsanitized user input to this method will result in a SQL
24 /// injection vulnerability. Always use hardcoded or application-controlled
25 /// expressions.
26 ///
27 /// # Examples
28 ///
29 /// ```
30 /// use reinhardt_db::hybrid::expression::SqlExpression;
31 ///
32 /// let expr = SqlExpression::new("SELECT * FROM users");
33 /// assert_eq!(expr.sql, "SELECT * FROM users");
34 ///
35 /// // Also works with String
36 /// let expr2 = SqlExpression::new(String::from("COUNT(*)"));
37 /// assert_eq!(expr2.sql, "COUNT(*)");
38 /// ```
39 pub fn new(sql: impl Into<String>) -> Self {
40 Self { sql: sql.into() }
41 }
42 /// Creates a CONCAT SQL expression from multiple parts
43 ///
44 /// # Safety
45 ///
46 /// Each element in `parts` is embedded directly into the generated SQL
47 /// without escaping. The caller **must** ensure all parts are trusted
48 /// column names or SQL literals, not user-controlled data.
49 ///
50 /// # SQL Injection Risk
51 ///
52 /// Passing unsanitized user input in any element of `parts` will result
53 /// in a SQL injection vulnerability.
54 ///
55 /// # Examples
56 ///
57 /// ```
58 /// use reinhardt_db::hybrid::expression::SqlExpression;
59 ///
60 /// let expr = SqlExpression::concat(&["first_name", "' '", "last_name"]);
61 /// assert_eq!(expr.sql, "CONCAT(first_name, ' ', last_name)");
62 ///
63 /// // Single part
64 /// let expr2 = SqlExpression::concat(&["column1"]);
65 /// assert_eq!(expr2.sql, "CONCAT(column1)");
66 /// ```
67 pub fn concat(parts: &[&str]) -> Self {
68 Self {
69 sql: format!("CONCAT({})", parts.join(", ")),
70 }
71 }
72 /// Creates a LOWER SQL expression for case-insensitive operations
73 ///
74 /// # Safety
75 ///
76 /// The `column` argument is embedded directly into the generated SQL
77 /// without escaping. The caller **must** ensure it is a trusted column
78 /// name, not user-controlled data.
79 ///
80 /// # SQL Injection Risk
81 ///
82 /// Passing unsanitized user input as `column` will result in a SQL
83 /// injection vulnerability.
84 ///
85 /// # Examples
86 ///
87 /// ```
88 /// use reinhardt_db::hybrid::expression::SqlExpression;
89 ///
90 /// let expr = SqlExpression::lower("email");
91 /// assert_eq!(expr.sql, "LOWER(email)");
92 /// ```
93 pub fn lower(column: &str) -> Self {
94 Self {
95 sql: format!("LOWER({})", column),
96 }
97 }
98 /// Creates an UPPER SQL expression for case-insensitive operations
99 ///
100 /// # Safety
101 ///
102 /// The `column` argument is embedded directly into the generated SQL
103 /// without escaping. The caller **must** ensure it is a trusted column
104 /// name, not user-controlled data.
105 ///
106 /// # SQL Injection Risk
107 ///
108 /// Passing unsanitized user input as `column` will result in a SQL
109 /// injection vulnerability.
110 ///
111 /// # Examples
112 ///
113 /// ```
114 /// use reinhardt_db::hybrid::expression::SqlExpression;
115 ///
116 /// let expr = SqlExpression::upper("name");
117 /// assert_eq!(expr.sql, "UPPER(name)");
118 /// ```
119 pub fn upper(column: &str) -> Self {
120 Self {
121 sql: format!("UPPER({})", column),
122 }
123 }
124 /// Creates a COALESCE SQL expression to handle NULL values
125 ///
126 /// # Safety
127 ///
128 /// Both `column` and `default` are embedded directly into the generated SQL
129 /// without escaping. The caller **must** ensure both arguments are trusted
130 /// column names or SQL literals, not user-controlled data.
131 ///
132 /// # SQL Injection Risk
133 ///
134 /// Passing unsanitized user input as either argument will result in a SQL
135 /// injection vulnerability.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use reinhardt_db::hybrid::expression::SqlExpression;
141 ///
142 /// let expr = SqlExpression::coalesce("middle_name", "'N/A'");
143 /// assert_eq!(expr.sql, "COALESCE(middle_name, 'N/A')");
144 /// ```
145 pub fn coalesce(column: &str, default: &str) -> Self {
146 Self {
147 sql: format!("COALESCE({}, {})", column, default),
148 }
149 }
150}
151
152/// Trait for types that can be converted to SQL expressions
153pub trait Expression {
154 /// Converts the expression to its SQL representation.
155 fn to_sql(&self) -> String;
156}
157
158impl Expression for SqlExpression {
159 fn to_sql(&self) -> String {
160 self.sql.clone()
161 }
162}
163
164impl Expression for String {
165 fn to_sql(&self) -> String {
166 self.clone()
167 }
168}
169
170impl Expression for &str {
171 fn to_sql(&self) -> String {
172 self.to_string()
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_hybrid_expression_lower_unit() {
182 let expr = SqlExpression::lower("email");
183 assert_eq!(expr.sql, "LOWER(email)");
184 }
185}