Skip to main content

reinhardt_query/types/
operators.rs

1//! SQL operators for expressions.
2//!
3//! This module provides operators used in SQL expressions:
4//!
5//! - [`UnOper`]: Unary operators (NOT, etc.)
6//! - [`BinOper`]: Binary operators (AND, OR, =, <, etc.)
7//! - [`LogicalChainOper`]: Operators for chaining conditions
8//! - [`SubQueryOper`]: Subquery operators (EXISTS, ANY, ALL, SOME)
9
10/// Unary operators.
11///
12/// These operators take a single operand.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum UnOper {
15	/// Logical NOT
16	Not,
17}
18
19impl UnOper {
20	/// Returns the SQL representation of this operator.
21	#[must_use]
22	pub fn as_str(&self) -> &'static str {
23		match self {
24			Self::Not => "NOT",
25		}
26	}
27}
28
29/// Binary operators.
30///
31/// These operators take two operands.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub enum BinOper {
34	// Logical operators
35	/// Logical AND
36	And,
37	/// Logical OR
38	Or,
39
40	// Comparison operators
41	/// Equal (=)
42	Equal,
43	/// Not equal (<>)
44	NotEqual,
45	/// Less than (<)
46	SmallerThan,
47	/// Less than or equal (<=)
48	SmallerThanOrEqual,
49	/// Greater than (>)
50	GreaterThan,
51	/// Greater than or equal (>=)
52	GreaterThanOrEqual,
53
54	// Pattern matching
55	/// LIKE
56	Like,
57	/// NOT LIKE
58	NotLike,
59	/// ILIKE (case-insensitive LIKE, PostgreSQL)
60	ILike,
61	/// NOT ILIKE (PostgreSQL)
62	NotILike,
63	/// SIMILAR TO (PostgreSQL)
64	SimilarTo,
65	/// NOT SIMILAR TO (PostgreSQL)
66	NotSimilarTo,
67	/// Regex match (~ in PostgreSQL)
68	Matches,
69	/// Regex not match (!~ in PostgreSQL)
70	NotMatches,
71
72	// Set membership
73	/// IN
74	In,
75	/// NOT IN
76	NotIn,
77	/// BETWEEN
78	Between,
79	/// NOT BETWEEN
80	NotBetween,
81
82	// NULL checks
83	/// IS
84	Is,
85	/// IS NOT
86	IsNot,
87
88	// Arithmetic operators
89	/// Addition (+)
90	Add,
91	/// Subtraction (-)
92	Sub,
93	/// Multiplication (*)
94	Mul,
95	/// Division (/)
96	Div,
97	/// Modulo (%)
98	Mod,
99
100	// Bit operators
101	/// Bitwise AND (&)
102	BitAnd,
103	/// Bitwise OR (|)
104	BitOr,
105	/// Left shift (<<)
106	LShift,
107	/// Right shift (>>)
108	RShift,
109
110	// Array operators (PostgreSQL)
111	/// Array contains (@>)
112	PgOperator(PgBinOper),
113}
114
115/// PostgreSQL-specific binary operators.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub enum PgBinOper {
118	/// Contains (@>)
119	Contains,
120	/// Contained by (<@)
121	Contained,
122	/// Overlap (&&)
123	Overlap,
124	/// Concatenate (||)
125	Concatenate,
126	/// JSONB key exists (?)
127	JsonContainsKey,
128	/// JSONB any key exists (?|)
129	JsonContainsAnyKey,
130	/// JSONB all keys exist (?&)
131	JsonContainsAllKeys,
132	/// Get JSON element (->)
133	JsonGetByIndex,
134	/// Get JSON element as text (->>)
135	JsonGetAsText,
136	/// Get JSON path (#>)
137	JsonGetPath,
138	/// Get JSON path as text (#>>)
139	JsonGetPathAsText,
140}
141
142impl BinOper {
143	/// Returns the SQL representation of this operator.
144	#[must_use]
145	pub fn as_str(&self) -> &'static str {
146		match self {
147			Self::And => "AND",
148			Self::Or => "OR",
149			Self::Equal => "=",
150			Self::NotEqual => "<>",
151			Self::SmallerThan => "<",
152			Self::SmallerThanOrEqual => "<=",
153			Self::GreaterThan => ">",
154			Self::GreaterThanOrEqual => ">=",
155			Self::Like => "LIKE",
156			Self::NotLike => "NOT LIKE",
157			Self::ILike => "ILIKE",
158			Self::NotILike => "NOT ILIKE",
159			Self::SimilarTo => "SIMILAR TO",
160			Self::NotSimilarTo => "NOT SIMILAR TO",
161			Self::Matches => "~",
162			Self::NotMatches => "!~",
163			Self::In => "IN",
164			Self::NotIn => "NOT IN",
165			Self::Between => "BETWEEN",
166			Self::NotBetween => "NOT BETWEEN",
167			Self::Is => "IS",
168			Self::IsNot => "IS NOT",
169			Self::Add => "+",
170			Self::Sub => "-",
171			Self::Mul => "*",
172			Self::Div => "/",
173			Self::Mod => "%",
174			Self::BitAnd => "&",
175			Self::BitOr => "|",
176			Self::LShift => "<<",
177			Self::RShift => ">>",
178			Self::PgOperator(pg_op) => pg_op.as_str(),
179		}
180	}
181
182	/// Returns the precedence of this operator.
183	///
184	/// Higher values indicate higher precedence (binds more tightly).
185	#[must_use]
186	pub fn precedence(&self) -> u8 {
187		match self {
188			Self::Or => 1,
189			Self::And => 2,
190			Self::Is | Self::IsNot => 3,
191			Self::Between | Self::NotBetween | Self::In | Self::NotIn => 4,
192			Self::Like
193			| Self::NotLike
194			| Self::ILike
195			| Self::NotILike
196			| Self::SimilarTo
197			| Self::NotSimilarTo
198			| Self::Matches
199			| Self::NotMatches => 5,
200			Self::Equal
201			| Self::NotEqual
202			| Self::SmallerThan
203			| Self::SmallerThanOrEqual
204			| Self::GreaterThan
205			| Self::GreaterThanOrEqual => 6,
206			Self::BitOr => 7,
207			Self::BitAnd => 8,
208			Self::LShift | Self::RShift => 9,
209			Self::Add | Self::Sub => 10,
210			Self::Mul | Self::Div | Self::Mod => 11,
211			Self::PgOperator(_) => 6, // Same as comparison
212		}
213	}
214
215	/// Returns whether this operator is left-associative.
216	#[must_use]
217	pub fn is_left_associative(&self) -> bool {
218		// All binary operators in SQL are left-associative
219		true
220	}
221}
222
223impl PgBinOper {
224	/// Returns the SQL representation of this PostgreSQL operator.
225	#[must_use]
226	pub fn as_str(&self) -> &'static str {
227		match self {
228			Self::Contains => "@>",
229			Self::Contained => "<@",
230			Self::Overlap => "&&",
231			Self::Concatenate => "||",
232			Self::JsonContainsKey => "?",
233			Self::JsonContainsAnyKey => "?|",
234			Self::JsonContainsAllKeys => "?&",
235			Self::JsonGetByIndex => "->",
236			Self::JsonGetAsText => "->>",
237			Self::JsonGetPath => "#>",
238			Self::JsonGetPathAsText => "#>>",
239		}
240	}
241}
242
243/// Logical operators for chaining conditions.
244///
245/// These operators connect conditions in WHERE clauses.
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
247pub enum LogicalChainOper {
248	/// Logical AND
249	And,
250	/// Logical OR
251	Or,
252}
253
254impl LogicalChainOper {
255	/// Returns the SQL representation of this operator.
256	#[must_use]
257	pub fn as_str(&self) -> &'static str {
258		match self {
259			Self::And => "AND",
260			Self::Or => "OR",
261		}
262	}
263}
264
265impl From<LogicalChainOper> for BinOper {
266	fn from(op: LogicalChainOper) -> Self {
267		match op {
268			LogicalChainOper::And => BinOper::And,
269			LogicalChainOper::Or => BinOper::Or,
270		}
271	}
272}
273
274/// Subquery operators.
275///
276/// These operators are used with subqueries.
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
278pub enum SubQueryOper {
279	/// EXISTS
280	Exists,
281	/// ANY
282	Any,
283	/// SOME (alias for ANY)
284	Some,
285	/// ALL
286	All,
287}
288
289impl SubQueryOper {
290	/// Returns the SQL representation of this operator.
291	#[must_use]
292	pub fn as_str(&self) -> &'static str {
293		match self {
294			Self::Exists => "EXISTS",
295			Self::Any => "ANY",
296			Self::Some => "SOME",
297			Self::All => "ALL",
298		}
299	}
300}
301
302#[cfg(test)]
303mod tests {
304	use super::*;
305	use rstest::rstest;
306
307	#[rstest]
308	fn test_un_oper_as_str() {
309		assert_eq!(UnOper::Not.as_str(), "NOT");
310	}
311
312	#[rstest]
313	#[case(BinOper::And, "AND")]
314	#[case(BinOper::Or, "OR")]
315	#[case(BinOper::Equal, "=")]
316	#[case(BinOper::NotEqual, "<>")]
317	#[case(BinOper::SmallerThan, "<")]
318	#[case(BinOper::GreaterThan, ">")]
319	#[case(BinOper::Like, "LIKE")]
320	#[case(BinOper::In, "IN")]
321	#[case(BinOper::Between, "BETWEEN")]
322	fn test_bin_oper_as_str(#[case] op: BinOper, #[case] expected: &str) {
323		assert_eq!(op.as_str(), expected);
324	}
325
326	#[rstest]
327	fn test_bin_oper_precedence() {
328		// Multiplication has higher precedence than addition
329		assert!(BinOper::Mul.precedence() > BinOper::Add.precedence());
330		// AND has higher precedence than OR
331		assert!(BinOper::And.precedence() > BinOper::Or.precedence());
332		// Comparison has higher precedence than logical
333		assert!(BinOper::Equal.precedence() > BinOper::And.precedence());
334	}
335
336	#[rstest]
337	fn test_logical_chain_oper() {
338		assert_eq!(LogicalChainOper::And.as_str(), "AND");
339		assert_eq!(LogicalChainOper::Or.as_str(), "OR");
340	}
341
342	#[rstest]
343	fn test_subquery_oper() {
344		assert_eq!(SubQueryOper::Exists.as_str(), "EXISTS");
345		assert_eq!(SubQueryOper::Any.as_str(), "ANY");
346		assert_eq!(SubQueryOper::All.as_str(), "ALL");
347	}
348
349	#[rstest]
350	fn test_pg_bin_oper() {
351		assert_eq!(PgBinOper::Contains.as_str(), "@>");
352		assert_eq!(PgBinOper::Contained.as_str(), "<@");
353		assert_eq!(PgBinOper::JsonGetAsText.as_str(), "->>");
354	}
355}