Skip to main content

use_sql_expression/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6use use_sql_column::SqlColumnRef;
7use use_sql_ident::SqlIdentifier;
8use use_sql_operator::{
9    SqlComparisonOperator, SqlLogicalOperator, SqlNullOperator, SqlOperator, SqlPatternOperator,
10};
11use use_sql_param::SqlParameter;
12use use_sql_value::SqlValue;
13
14/// Lightweight SQL expression container.
15#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
16pub struct SqlExpression {
17    kind: SqlExpressionKind,
18}
19
20impl SqlExpression {
21    /// Creates an expression from an identifier.
22    #[must_use]
23    pub const fn identifier(identifier: SqlIdentifier) -> Self {
24        Self {
25            kind: SqlExpressionKind::Identifier(identifier),
26        }
27    }
28
29    /// Creates an expression from a column reference.
30    #[must_use]
31    pub const fn column(column: SqlColumnRef) -> Self {
32        Self {
33            kind: SqlExpressionKind::Column(column),
34        }
35    }
36
37    /// Creates an expression from a literal value.
38    #[must_use]
39    pub const fn value(value: SqlValue) -> Self {
40        Self {
41            kind: SqlExpressionKind::Value(value),
42        }
43    }
44
45    /// Creates an expression from a parameter placeholder.
46    #[must_use]
47    pub const fn parameter(parameter: SqlParameter) -> Self {
48        Self {
49            kind: SqlExpressionKind::Parameter(parameter),
50        }
51    }
52
53    /// Creates a simple binary operator expression.
54    #[must_use]
55    pub fn binary(left: Self, operator: SqlOperator, right: Self) -> Self {
56        Self {
57            kind: SqlExpressionKind::Operator {
58                left: Box::new(left),
59                operator,
60                right: Box::new(right),
61            },
62        }
63    }
64
65    /// Creates an expression from a predicate.
66    #[must_use]
67    pub fn predicate(predicate: SqlPredicate) -> Self {
68        Self {
69            kind: SqlExpressionKind::Predicate(Box::new(predicate)),
70        }
71    }
72
73    /// Returns the expression kind.
74    #[must_use]
75    pub const fn kind(&self) -> &SqlExpressionKind {
76        &self.kind
77    }
78}
79
80impl fmt::Display for SqlExpression {
81    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match &self.kind {
83            SqlExpressionKind::Identifier(identifier) => identifier.fmt(formatter),
84            SqlExpressionKind::Column(column) => column.fmt(formatter),
85            SqlExpressionKind::Value(value) => value.fmt(formatter),
86            SqlExpressionKind::Parameter(parameter) => parameter.fmt(formatter),
87            SqlExpressionKind::Operator {
88                left,
89                operator,
90                right,
91            } => write!(formatter, "({left} {operator} {right})"),
92            SqlExpressionKind::Predicate(predicate) => predicate.fmt(formatter),
93        }
94    }
95}
96
97impl From<SqlIdentifier> for SqlExpression {
98    fn from(value: SqlIdentifier) -> Self {
99        Self::identifier(value)
100    }
101}
102
103impl From<SqlColumnRef> for SqlExpression {
104    fn from(value: SqlColumnRef) -> Self {
105        Self::column(value)
106    }
107}
108
109impl From<SqlValue> for SqlExpression {
110    fn from(value: SqlValue) -> Self {
111        Self::value(value)
112    }
113}
114
115impl From<SqlParameter> for SqlExpression {
116    fn from(value: SqlParameter) -> Self {
117        Self::parameter(value)
118    }
119}
120
121/// Lightweight SQL expression kinds.
122#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
123pub enum SqlExpressionKind {
124    Identifier(SqlIdentifier),
125    Column(SqlColumnRef),
126    Value(SqlValue),
127    Parameter(SqlParameter),
128    Operator {
129        left: Box<SqlExpression>,
130        operator: SqlOperator,
131        right: Box<SqlExpression>,
132    },
133    Predicate(Box<SqlPredicate>),
134}
135
136/// Lightweight SQL predicate containers.
137#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
138pub enum SqlPredicate {
139    Comparison {
140        left: Box<SqlExpression>,
141        operator: SqlComparisonOperator,
142        right: Box<SqlExpression>,
143    },
144    Null {
145        expression: Box<SqlExpression>,
146        operator: SqlNullOperator,
147    },
148    Pattern {
149        left: Box<SqlExpression>,
150        operator: SqlPatternOperator,
151        right: Box<SqlExpression>,
152    },
153    Logical {
154        left: Box<Self>,
155        operator: SqlLogicalOperator,
156        right: Box<Self>,
157    },
158    Not(Box<Self>),
159}
160
161impl SqlPredicate {
162    /// Creates a comparison predicate.
163    #[must_use]
164    pub fn comparison(
165        left: SqlExpression,
166        operator: SqlComparisonOperator,
167        right: SqlExpression,
168    ) -> Self {
169        Self::Comparison {
170            left: Box::new(left),
171            operator,
172            right: Box::new(right),
173        }
174    }
175
176    /// Creates a null-check predicate.
177    #[must_use]
178    pub fn null(expression: SqlExpression, operator: SqlNullOperator) -> Self {
179        Self::Null {
180            expression: Box::new(expression),
181            operator,
182        }
183    }
184
185    /// Creates a pattern or membership predicate.
186    #[must_use]
187    pub fn pattern(
188        left: SqlExpression,
189        operator: SqlPatternOperator,
190        right: SqlExpression,
191    ) -> Self {
192        Self::Pattern {
193            left: Box::new(left),
194            operator,
195            right: Box::new(right),
196        }
197    }
198
199    /// Creates a logical predicate.
200    #[must_use]
201    pub fn logical(left: Self, operator: SqlLogicalOperator, right: Self) -> Self {
202        Self::Logical {
203            left: Box::new(left),
204            operator,
205            right: Box::new(right),
206        }
207    }
208
209    /// Creates a negated predicate.
210    #[must_use]
211    pub fn negate(predicate: Self) -> Self {
212        Self::Not(Box::new(predicate))
213    }
214}
215
216impl fmt::Display for SqlPredicate {
217    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218        match self {
219            Self::Comparison {
220                left,
221                operator,
222                right,
223            } => write!(formatter, "{left} {operator} {right}"),
224            Self::Null {
225                expression,
226                operator,
227            } => write!(formatter, "{expression} {operator}"),
228            Self::Pattern {
229                left,
230                operator,
231                right,
232            } => write!(formatter, "{left} {operator} {right}"),
233            Self::Logical {
234                left,
235                operator,
236                right,
237            } => write!(formatter, "({left}) {operator} ({right})"),
238            Self::Not(predicate) => write!(formatter, "NOT ({predicate})"),
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::{SqlExpression, SqlPredicate};
246    use use_sql_column::{SqlColumnName, SqlColumnRef};
247    use use_sql_operator::{SqlComparisonOperator, SqlNullOperator};
248    use use_sql_param::SqlParameter;
249    use use_sql_value::SqlValue;
250
251    #[test]
252    fn renders_simple_expressions() -> Result<(), Box<dyn std::error::Error>> {
253        let column = SqlExpression::column(SqlColumnRef::new(SqlColumnName::new("id")?));
254        let parameter = SqlExpression::parameter("$1".parse::<SqlParameter>()?);
255        let predicate = SqlPredicate::comparison(column, SqlComparisonOperator::Equal, parameter);
256
257        assert_eq!(predicate.to_string(), "id = $1");
258        assert_eq!(
259            SqlExpression::value(SqlValue::string("Ada")).to_string(),
260            "'Ada'"
261        );
262        Ok(())
263    }
264
265    #[test]
266    fn renders_null_predicates() -> Result<(), Box<dyn std::error::Error>> {
267        let column = SqlExpression::column(SqlColumnRef::new(SqlColumnName::new("deleted_at")?));
268        let predicate = SqlPredicate::null(column, SqlNullOperator::IsNull);
269
270        assert_eq!(predicate.to_string(), "deleted_at IS NULL");
271        Ok(())
272    }
273}