Skip to main content

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}