Skip to main content

reinhardt_query/expr/
simple_expr.rs

1//! SimpleExpr - The core expression AST.
2//!
3//! This module defines [`SimpleExpr`], which represents SQL expressions as an
4//! abstract syntax tree (AST). All expression operations eventually produce
5//! a `SimpleExpr`.
6
7use crate::types::{BinOper, ColumnRef, DynIden, UnOper, WindowStatement};
8use crate::value::Value;
9
10/// Subquery operators used in SQL expressions
11///
12/// These operators are used to combine subqueries with other expressions.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum SubQueryOper {
15	/// EXISTS (subquery)
16	Exists,
17	/// NOT EXISTS (subquery)
18	NotExists,
19	/// IN (subquery)
20	In,
21	/// NOT IN (subquery)
22	NotIn,
23	/// ALL (subquery) - used with comparison operators
24	All,
25	/// ANY (subquery) - used with comparison operators
26	Any,
27	/// SOME (subquery) - alias for ANY
28	Some,
29}
30
31/// A simple SQL expression.
32///
33/// This enum represents the AST for SQL expressions. Each variant corresponds
34/// to a type of SQL expression that can appear in queries.
35///
36/// # Example
37///
38/// ```rust,ignore
39/// use reinhardt_query::SimpleExpr;
40///
41/// // Column reference
42/// let col = SimpleExpr::Column(ColumnRef::column("name"));
43///
44/// // Value literal
45/// let val = SimpleExpr::Value(Value::Int(Some(42)));
46///
47/// // Binary operation (column = 42)
48/// let eq = SimpleExpr::Binary(
49///     Box::new(col),
50///     BinOper::Equal,
51///     Box::new(val),
52/// );
53/// ```
54#[derive(Debug, Clone)]
55pub enum SimpleExpr {
56	/// A column reference (e.g., `name`, `users.name`, `public.users.name`)
57	Column(ColumnRef),
58
59	/// A table-qualified column (legacy format)
60	TableColumn(DynIden, DynIden),
61
62	/// A literal value (e.g., `42`, `'hello'`, `TRUE`)
63	Value(Value),
64
65	/// A unary operation (e.g., `NOT x`)
66	Unary(UnOper, Box<SimpleExpr>),
67
68	/// A binary operation (e.g., `x = y`, `a AND b`, `x + y`)
69	Binary(Box<SimpleExpr>, BinOper, Box<SimpleExpr>),
70
71	/// A function call (e.g., `MAX(x)`, `LOWER(name)`)
72	FunctionCall(DynIden, Vec<SimpleExpr>),
73
74	/// A subquery (e.g., `(SELECT ...)`)
75	///
76	/// The optional operator indicates how the subquery is used:
77	/// - `None`: Standalone subquery (e.g., in FROM clause or SELECT list)
78	/// - `Some(SubQueryOper)`: Subquery with operator (e.g., IN, EXISTS, ALL)
79	SubQuery(Option<SubQueryOper>, Box<crate::query::SelectStatement>),
80
81	/// A tuple of expressions (e.g., `(1, 2, 3)`)
82	Tuple(Vec<SimpleExpr>),
83
84	/// A custom SQL expression (e.g., `NOW()`)
85	///
86	/// # Security Warning
87	///
88	/// This variant embeds raw SQL directly into the query. Only use with trusted
89	/// input or static SQL strings. For dynamic values, use `CustomWithExpr` instead.
90	Custom(String),
91
92	/// A custom SQL expression with parameter placeholders
93	CustomWithExpr(String, Vec<SimpleExpr>),
94
95	/// A constant (database-specific constant like `TRUE`, `FALSE`, `NULL`)
96	Constant(Keyword),
97
98	/// An asterisk (`*`)
99	Asterisk,
100
101	/// A CASE WHEN expression
102	Case(Box<CaseStatement>),
103
104	/// An AS expression with alias (e.g., `expr AS alias`)
105	AsEnum(DynIden, Box<SimpleExpr>),
106
107	/// A CAST expression (e.g., `CAST(x AS INTEGER)`)
108	Cast(Box<SimpleExpr>, DynIden),
109
110	/// A window function with inline window specification
111	///
112	/// Represents expressions like:
113	/// ```sql
114	/// ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC)
115	/// ```
116	Window {
117		/// The function expression
118		func: Box<SimpleExpr>,
119		/// The window specification
120		window: WindowStatement,
121	},
122
123	/// A window function with named window reference
124	///
125	/// Represents expressions like:
126	/// ```sql
127	/// ROW_NUMBER() OVER window_name
128	/// ```
129	/// where `window_name` is defined in the WINDOW clause.
130	WindowNamed {
131		/// The function expression
132		func: Box<SimpleExpr>,
133		/// The window name
134		name: DynIden,
135	},
136}
137
138/// SQL keywords that can appear as constants.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum Keyword {
141	/// SQL NULL
142	Null,
143	/// SQL TRUE
144	True,
145	/// SQL FALSE
146	False,
147	/// SQL DEFAULT
148	Default,
149	/// SQL CURRENT_TIMESTAMP
150	CurrentTimestamp,
151	/// SQL CURRENT_DATE
152	CurrentDate,
153	/// SQL CURRENT_TIME
154	CurrentTime,
155}
156
157impl Keyword {
158	/// Returns the SQL representation of this keyword.
159	#[must_use]
160	pub fn as_str(&self) -> &'static str {
161		match self {
162			Self::Null => "NULL",
163			Self::True => "TRUE",
164			Self::False => "FALSE",
165			Self::Default => "DEFAULT",
166			Self::CurrentTimestamp => "CURRENT_TIMESTAMP",
167			Self::CurrentDate => "CURRENT_DATE",
168			Self::CurrentTime => "CURRENT_TIME",
169		}
170	}
171}
172
173/// A CASE WHEN statement.
174///
175/// Represents SQL CASE expressions:
176/// ```sql
177/// CASE
178///     WHEN condition1 THEN result1
179///     WHEN condition2 THEN result2
180///     ELSE default_result
181/// END
182/// ```
183#[derive(Debug, Clone, Default)]
184pub struct CaseStatement {
185	/// The WHEN conditions and their THEN results
186	pub when_clauses: Vec<(SimpleExpr, SimpleExpr)>,
187	/// The ELSE result (optional)
188	pub else_clause: Option<SimpleExpr>,
189}
190
191impl CaseStatement {
192	/// Create a new empty CASE statement.
193	pub fn new() -> Self {
194		Self::default()
195	}
196
197	/// Add a WHEN clause.
198	#[must_use]
199	pub fn when<C, R>(mut self, condition: C, result: R) -> Self
200	where
201		C: Into<SimpleExpr>,
202		R: Into<SimpleExpr>,
203	{
204		self.when_clauses.push((condition.into(), result.into()));
205		self
206	}
207
208	/// Set the ELSE clause.
209	#[must_use]
210	pub fn else_result<E>(mut self, result: E) -> Self
211	where
212		E: Into<SimpleExpr>,
213	{
214		self.else_clause = Some(result.into());
215		self
216	}
217}
218
219impl SimpleExpr {
220	/// Create a binary operation expression.
221	pub fn binary(self, op: crate::types::BinOper, right: SimpleExpr) -> Self {
222		Self::Binary(Box::new(self), op, Box::new(right))
223	}
224}
225
226// Conversion implementations
227
228impl From<Value> for SimpleExpr {
229	fn from(v: Value) -> Self {
230		Self::Value(v)
231	}
232}
233
234impl From<ColumnRef> for SimpleExpr {
235	fn from(c: ColumnRef) -> Self {
236		Self::Column(c)
237	}
238}
239
240impl From<bool> for SimpleExpr {
241	fn from(b: bool) -> Self {
242		Self::Value(Value::Bool(Some(b)))
243	}
244}
245
246impl From<i32> for SimpleExpr {
247	fn from(i: i32) -> Self {
248		Self::Value(Value::Int(Some(i)))
249	}
250}
251
252impl From<i64> for SimpleExpr {
253	fn from(i: i64) -> Self {
254		Self::Value(Value::BigInt(Some(i)))
255	}
256}
257
258impl From<f32> for SimpleExpr {
259	fn from(f: f32) -> Self {
260		Self::Value(Value::Float(Some(f)))
261	}
262}
263
264impl From<f64> for SimpleExpr {
265	fn from(f: f64) -> Self {
266		Self::Value(Value::Double(Some(f)))
267	}
268}
269
270impl From<&str> for SimpleExpr {
271	fn from(s: &str) -> Self {
272		Self::Value(Value::String(Some(Box::new(s.to_string()))))
273	}
274}
275
276impl From<String> for SimpleExpr {
277	fn from(s: String) -> Self {
278		Self::Value(Value::String(Some(Box::new(s))))
279	}
280}
281
282impl From<Keyword> for SimpleExpr {
283	fn from(k: Keyword) -> Self {
284		Self::Constant(k)
285	}
286}
287
288impl From<CaseStatement> for SimpleExpr {
289	fn from(case: CaseStatement) -> Self {
290		Self::Case(Box::new(case))
291	}
292}
293
294#[cfg(test)]
295mod tests {
296	use super::*;
297	use rstest::rstest;
298
299	#[rstest]
300	fn test_simple_expr_from_value() {
301		let expr: SimpleExpr = Value::Int(Some(42)).into();
302		assert!(matches!(expr, SimpleExpr::Value(Value::Int(Some(42)))));
303	}
304
305	#[rstest]
306	fn test_simple_expr_from_bool() {
307		let expr: SimpleExpr = true.into();
308		assert!(matches!(expr, SimpleExpr::Value(Value::Bool(Some(true)))));
309	}
310
311	#[rstest]
312	fn test_simple_expr_from_i32() {
313		let expr: SimpleExpr = 42i32.into();
314		assert!(matches!(expr, SimpleExpr::Value(Value::Int(Some(42)))));
315	}
316
317	#[rstest]
318	fn test_simple_expr_from_str() {
319		let expr: SimpleExpr = "hello".into();
320		if let SimpleExpr::Value(Value::String(Some(s))) = expr {
321			assert_eq!(*s, "hello");
322		} else {
323			panic!("Expected String value");
324		}
325	}
326
327	#[rstest]
328	fn test_simple_expr_column() {
329		let col = ColumnRef::column("name");
330		let expr: SimpleExpr = col.into();
331		assert!(matches!(expr, SimpleExpr::Column(_)));
332	}
333
334	#[rstest]
335	fn test_keyword_as_str() {
336		assert_eq!(Keyword::Null.as_str(), "NULL");
337		assert_eq!(Keyword::True.as_str(), "TRUE");
338		assert_eq!(Keyword::False.as_str(), "FALSE");
339		assert_eq!(Keyword::CurrentTimestamp.as_str(), "CURRENT_TIMESTAMP");
340	}
341
342	#[rstest]
343	fn test_case_statement_builder() {
344		let case = CaseStatement::new()
345			.when(true, 1i32)
346			.when(false, 0i32)
347			.else_result(-1i32);
348
349		assert_eq!(case.when_clauses.len(), 2);
350		assert!(case.else_clause.is_some());
351	}
352
353	#[rstest]
354	fn test_simple_expr_binary() {
355		let left = SimpleExpr::Column(ColumnRef::column("age"));
356		let right = SimpleExpr::Value(Value::Int(Some(18)));
357		let binary =
358			SimpleExpr::Binary(Box::new(left), BinOper::GreaterThanOrEqual, Box::new(right));
359
360		assert!(matches!(
361			binary,
362			SimpleExpr::Binary(_, BinOper::GreaterThanOrEqual, _)
363		));
364	}
365
366	#[rstest]
367	fn test_simple_expr_unary() {
368		let inner = SimpleExpr::Value(Value::Bool(Some(true)));
369		let unary = SimpleExpr::Unary(UnOper::Not, Box::new(inner));
370
371		assert!(matches!(unary, SimpleExpr::Unary(UnOper::Not, _)));
372	}
373}