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