1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
9pub enum SqlComparisonOperator {
10 #[default]
11 Equal,
12 NotEqual,
13 NotEqualBang,
14 Less,
15 LessOrEqual,
16 Greater,
17 GreaterOrEqual,
18}
19
20impl SqlComparisonOperator {
21 #[must_use]
23 pub const fn as_str(self) -> &'static str {
24 match self {
25 Self::Equal => "=",
26 Self::NotEqual => "<>",
27 Self::NotEqualBang => "!=",
28 Self::Less => "<",
29 Self::LessOrEqual => "<=",
30 Self::Greater => ">",
31 Self::GreaterOrEqual => ">=",
32 }
33 }
34}
35
36impl fmt::Display for SqlComparisonOperator {
37 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 formatter.write_str(self.as_str())
39 }
40}
41
42#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
44pub enum SqlLogicalOperator {
45 #[default]
46 And,
47 Or,
48 Not,
49}
50
51impl SqlLogicalOperator {
52 #[must_use]
54 pub const fn as_str(self) -> &'static str {
55 match self {
56 Self::And => "AND",
57 Self::Or => "OR",
58 Self::Not => "NOT",
59 }
60 }
61}
62
63impl fmt::Display for SqlLogicalOperator {
64 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65 formatter.write_str(self.as_str())
66 }
67}
68
69#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
71pub enum SqlNullOperator {
72 #[default]
73 IsNull,
74 IsNotNull,
75}
76
77impl SqlNullOperator {
78 #[must_use]
80 pub const fn as_str(self) -> &'static str {
81 match self {
82 Self::IsNull => "IS NULL",
83 Self::IsNotNull => "IS NOT NULL",
84 }
85 }
86}
87
88impl fmt::Display for SqlNullOperator {
89 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90 formatter.write_str(self.as_str())
91 }
92}
93
94#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
96pub enum SqlPatternOperator {
97 #[default]
98 Like,
99 In,
100}
101
102impl SqlPatternOperator {
103 #[must_use]
105 pub const fn as_str(self) -> &'static str {
106 match self {
107 Self::Like => "LIKE",
108 Self::In => "IN",
109 }
110 }
111}
112
113impl fmt::Display for SqlPatternOperator {
114 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
115 formatter.write_str(self.as_str())
116 }
117}
118
119#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
121pub enum SqlOperator {
122 Comparison(SqlComparisonOperator),
123 Logical(SqlLogicalOperator),
124 Null(SqlNullOperator),
125 Pattern(SqlPatternOperator),
126}
127
128impl SqlOperator {
129 #[must_use]
131 pub const fn as_str(self) -> &'static str {
132 match self {
133 Self::Comparison(operator) => operator.as_str(),
134 Self::Logical(operator) => operator.as_str(),
135 Self::Null(operator) => operator.as_str(),
136 Self::Pattern(operator) => operator.as_str(),
137 }
138 }
139}
140
141impl fmt::Display for SqlOperator {
142 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143 formatter.write_str(self.as_str())
144 }
145}
146
147impl FromStr for SqlOperator {
148 type Err = SqlOperatorParseError;
149
150 fn from_str(input: &str) -> Result<Self, Self::Err> {
151 match normalized_operator(input)?.as_str() {
152 "=" => Ok(Self::Comparison(SqlComparisonOperator::Equal)),
153 "<>" => Ok(Self::Comparison(SqlComparisonOperator::NotEqual)),
154 "!=" => Ok(Self::Comparison(SqlComparisonOperator::NotEqualBang)),
155 "<" => Ok(Self::Comparison(SqlComparisonOperator::Less)),
156 "<=" => Ok(Self::Comparison(SqlComparisonOperator::LessOrEqual)),
157 ">" => Ok(Self::Comparison(SqlComparisonOperator::Greater)),
158 ">=" => Ok(Self::Comparison(SqlComparisonOperator::GreaterOrEqual)),
159 "AND" => Ok(Self::Logical(SqlLogicalOperator::And)),
160 "OR" => Ok(Self::Logical(SqlLogicalOperator::Or)),
161 "NOT" => Ok(Self::Logical(SqlLogicalOperator::Not)),
162 "LIKE" => Ok(Self::Pattern(SqlPatternOperator::Like)),
163 "IN" => Ok(Self::Pattern(SqlPatternOperator::In)),
164 "IS NULL" => Ok(Self::Null(SqlNullOperator::IsNull)),
165 "IS NOT NULL" => Ok(Self::Null(SqlNullOperator::IsNotNull)),
166 _ => Err(SqlOperatorParseError::Unknown),
167 }
168 }
169}
170
171impl TryFrom<&str> for SqlOperator {
172 type Error = SqlOperatorParseError;
173
174 fn try_from(value: &str) -> Result<Self, Self::Error> {
175 value.parse()
176 }
177}
178
179#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub enum SqlOperatorParseError {
182 Empty,
183 Unknown,
184}
185
186impl fmt::Display for SqlOperatorParseError {
187 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188 match self {
189 Self::Empty => formatter.write_str("SQL operator cannot be empty"),
190 Self::Unknown => formatter.write_str("unknown SQL operator"),
191 }
192 }
193}
194
195impl Error for SqlOperatorParseError {}
196
197fn normalized_operator(input: &str) -> Result<String, SqlOperatorParseError> {
198 let trimmed = input.trim();
199 if trimmed.is_empty() {
200 return Err(SqlOperatorParseError::Empty);
201 }
202 Ok(trimmed
203 .split_whitespace()
204 .collect::<Vec<_>>()
205 .join(" ")
206 .to_ascii_uppercase())
207}
208
209#[cfg(test)]
210mod tests {
211 use super::{SqlComparisonOperator, SqlOperator, SqlOperatorParseError};
212
213 #[test]
214 fn parses_operator_labels() -> Result<(), SqlOperatorParseError> {
215 assert_eq!("<=".parse::<SqlOperator>()?.to_string(), "<=");
216 assert_eq!(
217 "is not null".parse::<SqlOperator>()?.to_string(),
218 "IS NOT NULL"
219 );
220 assert_eq!(SqlComparisonOperator::NotEqualBang.to_string(), "!=");
221 Ok(())
222 }
223
224 #[test]
225 fn rejects_unknown_operators() {
226 assert_eq!("".parse::<SqlOperator>(), Err(SqlOperatorParseError::Empty));
227 assert_eq!(
228 "~~".parse::<SqlOperator>(),
229 Err(SqlOperatorParseError::Unknown)
230 );
231 }
232}