1use crate::select::Select;
4use crate::value::Value;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum RelationFilterOp {
9 Some,
11 None,
13 Every,
15}
16
17#[derive(Debug, Clone, PartialEq)]
19pub struct RelationFilter {
20 pub field: String,
22 pub parent_table: String,
24 pub target_table: String,
26 pub fk_db: String,
28 pub pk_db: String,
30 pub filter: Box<Expr>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum BinaryOp {
37 Eq,
39 Ne,
41 Lt,
43 Le,
45 Gt,
47 Ge,
49 And,
51 Or,
53 Like,
55 ArrayContains,
57 ArrayContainedBy,
59 ArrayOverlaps,
61 In,
63 NotIn,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub enum Expr {
70 Column(String),
72 Param(Value),
74 Binary {
76 left: Box<Expr>,
78 op: BinaryOp,
80 right: Box<Expr>,
82 },
83 Not(Box<Expr>),
85 FunctionCall {
87 name: String,
89 args: Vec<Expr>,
91 },
92 Filter {
94 expr: Box<Expr>,
96 predicate: Box<Expr>,
98 },
99 Exists(Box<Select>),
101 NotExists(Box<Select>),
103 Relation {
105 op: RelationFilterOp,
107 relation: Box<RelationFilter>,
109 },
110 ScalarSubquery(Box<Select>),
116 IsNull(Box<Expr>),
118 IsNotNull(Box<Expr>),
120 Literal(String),
126 List(Vec<Expr>),
131 CaseWhen {
133 condition: Box<Expr>,
135 then: Box<Expr>,
137 },
138 Star,
140}
141
142impl Expr {
143 pub fn column(name: impl Into<String>) -> Self {
145 Expr::Column(name.into())
146 }
147
148 pub fn param(value: impl Into<Value>) -> Self {
150 Expr::Param(value.into())
151 }
152
153 #[must_use]
155 pub fn eq(self, other: Expr) -> Self {
156 Expr::Binary {
157 left: Box::new(self),
158 op: BinaryOp::Eq,
159 right: Box::new(other),
160 }
161 }
162
163 #[must_use]
165 pub fn ne(self, other: Expr) -> Self {
166 Expr::Binary {
167 left: Box::new(self),
168 op: BinaryOp::Ne,
169 right: Box::new(other),
170 }
171 }
172
173 #[must_use]
175 pub fn lt(self, other: Expr) -> Self {
176 Expr::Binary {
177 left: Box::new(self),
178 op: BinaryOp::Lt,
179 right: Box::new(other),
180 }
181 }
182
183 #[must_use]
185 pub fn le(self, other: Expr) -> Self {
186 Expr::Binary {
187 left: Box::new(self),
188 op: BinaryOp::Le,
189 right: Box::new(other),
190 }
191 }
192
193 #[must_use]
195 pub fn gt(self, other: Expr) -> Self {
196 Expr::Binary {
197 left: Box::new(self),
198 op: BinaryOp::Gt,
199 right: Box::new(other),
200 }
201 }
202
203 #[must_use]
205 pub fn ge(self, other: Expr) -> Self {
206 Expr::Binary {
207 left: Box::new(self),
208 op: BinaryOp::Ge,
209 right: Box::new(other),
210 }
211 }
212
213 #[must_use]
215 pub fn and(self, other: Expr) -> Self {
216 Expr::Binary {
217 left: Box::new(self),
218 op: BinaryOp::And,
219 right: Box::new(other),
220 }
221 }
222
223 #[must_use]
225 pub fn or(self, other: Expr) -> Self {
226 Expr::Binary {
227 left: Box::new(self),
228 op: BinaryOp::Or,
229 right: Box::new(other),
230 }
231 }
232
233 #[must_use]
235 pub fn like(self, pattern: Expr) -> Self {
236 Expr::Binary {
237 left: Box::new(self),
238 op: BinaryOp::Like,
239 right: Box::new(pattern),
240 }
241 }
242
243 #[must_use]
245 pub fn in_list(self, exprs: Vec<Expr>) -> Self {
246 Expr::Binary {
247 left: Box::new(self),
248 op: BinaryOp::In,
249 right: Box::new(Expr::List(exprs)),
250 }
251 }
252
253 #[must_use]
255 pub fn not_in_list(self, exprs: Vec<Expr>) -> Self {
256 Expr::Binary {
257 left: Box::new(self),
258 op: BinaryOp::NotIn,
259 right: Box::new(Expr::List(exprs)),
260 }
261 }
262
263 pub fn function_call(name: impl Into<String>, args: Vec<Expr>) -> Self {
265 Expr::FunctionCall {
266 name: name.into(),
267 args,
268 }
269 }
270
271 pub fn json_agg(expr: Expr) -> Self {
273 Expr::FunctionCall {
274 name: "json_agg".to_string(),
275 args: vec![expr],
276 }
277 }
278
279 pub fn json_build_object(pairs: Vec<(String, Expr)>) -> Self {
289 let args: Vec<Expr> = pairs
290 .into_iter()
291 .flat_map(|(key, value)| vec![Expr::Literal(key), value])
292 .collect();
293
294 Expr::FunctionCall {
295 name: "json_build_object".to_string(),
296 args,
297 }
298 }
299
300 pub fn coalesce(exprs: Vec<Expr>) -> Self {
302 Expr::FunctionCall {
303 name: "COALESCE".to_string(),
304 args: exprs,
305 }
306 }
307
308 #[must_use]
310 pub fn is_not_null(self) -> Self {
311 Expr::IsNotNull(Box::new(self))
312 }
313
314 #[must_use]
316 pub fn is_null(self) -> Self {
317 Expr::IsNull(Box::new(self))
318 }
319
320 #[must_use]
322 pub fn filter(self, predicate: Expr) -> Self {
323 Expr::Filter {
324 expr: Box::new(self),
325 predicate: Box::new(predicate),
326 }
327 }
328
329 pub fn exists(subquery: Select) -> Self {
331 Expr::Exists(Box::new(subquery))
332 }
333
334 pub fn not_exists(subquery: Select) -> Self {
336 Expr::NotExists(Box::new(subquery))
337 }
338
339 pub fn relation_some(
341 field: impl Into<String>,
342 parent_table: impl Into<String>,
343 target_table: impl Into<String>,
344 fk_db: impl Into<String>,
345 pk_db: impl Into<String>,
346 filter: Expr,
347 ) -> Self {
348 Expr::Relation {
349 op: RelationFilterOp::Some,
350 relation: Box::new(RelationFilter {
351 field: field.into(),
352 parent_table: parent_table.into(),
353 target_table: target_table.into(),
354 fk_db: fk_db.into(),
355 pk_db: pk_db.into(),
356 filter: Box::new(filter),
357 }),
358 }
359 }
360
361 pub fn relation_none(
363 field: impl Into<String>,
364 parent_table: impl Into<String>,
365 target_table: impl Into<String>,
366 fk_db: impl Into<String>,
367 pk_db: impl Into<String>,
368 filter: Expr,
369 ) -> Self {
370 Expr::Relation {
371 op: RelationFilterOp::None,
372 relation: Box::new(RelationFilter {
373 field: field.into(),
374 parent_table: parent_table.into(),
375 target_table: target_table.into(),
376 fk_db: fk_db.into(),
377 pk_db: pk_db.into(),
378 filter: Box::new(filter),
379 }),
380 }
381 }
382
383 pub fn relation_every(
385 field: impl Into<String>,
386 parent_table: impl Into<String>,
387 target_table: impl Into<String>,
388 fk_db: impl Into<String>,
389 pk_db: impl Into<String>,
390 filter: Expr,
391 ) -> Self {
392 Expr::Relation {
393 op: RelationFilterOp::Every,
394 relation: Box::new(RelationFilter {
395 field: field.into(),
396 parent_table: parent_table.into(),
397 target_table: target_table.into(),
398 fk_db: fk_db.into(),
399 pk_db: pk_db.into(),
400 filter: Box::new(filter),
401 }),
402 }
403 }
404
405 pub fn scalar_subquery(subquery: Select) -> Self {
409 Expr::ScalarSubquery(Box::new(subquery))
410 }
411
412 pub fn case_when(condition: Expr, then: Expr) -> Self {
414 Expr::CaseWhen {
415 condition: Box::new(condition),
416 then: Box::new(then),
417 }
418 }
419
420 pub fn star() -> Self {
422 Expr::Star
423 }
424}
425
426impl std::ops::Not for Expr {
428 type Output = Self;
429
430 fn not(self) -> Self::Output {
431 Expr::Not(Box::new(self))
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use super::*;
438
439 #[test]
440 fn test_column_expr() {
441 let expr = Expr::column("email");
442 match expr {
443 Expr::Column(name) => assert_eq!(name, "email"),
444 _ => panic!("Expected Column variant"),
445 }
446 }
447
448 #[test]
449 fn test_param_expr() {
450 let expr = Expr::param(42i64);
451 match expr {
452 Expr::Param(Value::I64(42)) => {}
453 _ => panic!("Expected Param with I64(42)"),
454 }
455 }
456
457 #[test]
458 fn test_binary_ops() {
459 let col = Expr::column("age");
460 let val = Expr::param(18i64);
461
462 let expr = col.ge(val);
463 match expr {
464 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::Ge),
465 _ => panic!("Expected Binary expression"),
466 }
467 }
468
469 #[test]
470 fn test_complex_expr() {
471 let expr = Expr::column("age")
472 .ge(Expr::param(18i64))
473 .and(Expr::column("email").like(Expr::param("%@gmail.com")));
474
475 match expr {
476 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::And),
477 _ => panic!("Expected Binary AND expression"),
478 }
479 }
480
481 #[test]
482 fn test_not_expr() {
483 let expr = !Expr::column("active").eq(Expr::param(true));
484 match expr {
485 Expr::Not(_) => {}
486 _ => panic!("Expected Not expression"),
487 }
488 }
489
490 #[test]
491 fn test_in_list() {
492 let expr = Expr::column("status").in_list(vec![
493 Expr::param(Value::String("active".to_string())),
494 Expr::param(Value::String("pending".to_string())),
495 ]);
496 match expr {
497 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::In),
498 _ => panic!("Expected Binary IN expression"),
499 }
500 }
501
502 #[test]
503 fn test_not_in_list() {
504 let expr = Expr::column("role").not_in_list(vec![
505 Expr::param(Value::String("admin".to_string())),
506 Expr::param(Value::String("superuser".to_string())),
507 ]);
508 match expr {
509 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::NotIn),
510 _ => panic!("Expected Binary NOT IN expression"),
511 }
512 }
513
514 #[test]
515 fn test_relation_predicate() {
516 let expr = Expr::relation_some(
517 "posts",
518 "users",
519 "posts",
520 "author_id",
521 "id",
522 Expr::column("posts__published").eq(Expr::param(true)),
523 );
524 match expr {
525 Expr::Relation { op, relation } => {
526 assert_eq!(op, RelationFilterOp::Some);
527 assert_eq!(relation.field, "posts");
528 assert_eq!(relation.parent_table, "users");
529 assert_eq!(relation.target_table, "posts");
530 assert_eq!(relation.fk_db, "author_id");
531 assert_eq!(relation.pk_db, "id");
532 }
533 _ => panic!("Expected relation predicate"),
534 }
535 }
536}