1use crate::select::Select;
4use crate::value::Value;
5
6pub const VECTOR_L2_DISTANCE_FUNCTION: &str = "__nautilus_vector_l2_distance";
8pub const VECTOR_INNER_PRODUCT_FUNCTION: &str = "__nautilus_vector_inner_product";
10pub const VECTOR_COSINE_DISTANCE_FUNCTION: &str = "__nautilus_vector_cosine_distance";
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum JsonPathCast {
21 None,
23 Signed,
25 Double,
27 Decimal,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum RelationFilterOp {
34 Some,
36 None,
38 Every,
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub struct RelationFilter {
45 pub field: String,
47 pub parent_table: String,
49 pub target_table: String,
51 pub fk_db: String,
53 pub pk_db: String,
55 pub filter: Box<Expr>,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub enum BinaryOp {
62 Eq,
64 Ne,
66 Lt,
68 Le,
70 Gt,
72 Ge,
74 And,
76 Or,
78 Like,
80 ArrayContains,
82 ArrayContainedBy,
84 ArrayOverlaps,
86 In,
88 NotIn,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
102pub struct LiteralSql(String);
103
104impl LiteralSql {
105 #[must_use]
108 pub fn from_static(text: &'static str) -> Self {
109 Self(text.to_string())
110 }
111
112 #[must_use]
116 pub fn trusted(text: impl Into<String>) -> Self {
117 Self(text.into())
118 }
119
120 #[must_use]
122 pub fn as_str(&self) -> &str {
123 &self.0
124 }
125}
126
127#[derive(Debug, Clone, PartialEq)]
129pub enum Expr {
130 Column(String),
132 CompositeField {
137 table: String,
139 column: String,
141 field: String,
143 json_key: String,
145 json_cast: JsonPathCast,
147 },
148 Param(Value),
150 Binary {
152 left: Box<Expr>,
154 op: BinaryOp,
156 right: Box<Expr>,
158 },
159 Not(Box<Expr>),
161 FunctionCall {
163 name: String,
165 args: Vec<Expr>,
167 },
168 Filter {
170 expr: Box<Expr>,
172 predicate: Box<Expr>,
174 },
175 Exists(Box<Select>),
177 NotExists(Box<Select>),
179 Relation {
181 op: RelationFilterOp,
183 relation: Box<RelationFilter>,
185 },
186 ScalarSubquery(Box<Select>),
192 IsNull(Box<Expr>),
194 IsNotNull(Box<Expr>),
196 Literal(LiteralSql),
203 List(Vec<Expr>),
208 CaseWhen {
210 condition: Box<Expr>,
212 then: Box<Expr>,
214 },
215 Star,
217}
218
219impl Expr {
220 pub fn column(name: impl Into<String>) -> Self {
222 Expr::Column(name.into())
223 }
224
225 pub fn composite_field(
227 table: impl Into<String>,
228 column: impl Into<String>,
229 field: impl Into<String>,
230 json_key: impl Into<String>,
231 json_cast: JsonPathCast,
232 ) -> Self {
233 Expr::CompositeField {
234 table: table.into(),
235 column: column.into(),
236 field: field.into(),
237 json_key: json_key.into(),
238 json_cast,
239 }
240 }
241
242 pub fn param(value: impl Into<Value>) -> Self {
244 Expr::Param(value.into())
245 }
246
247 #[must_use]
249 pub fn eq(self, other: Expr) -> Self {
250 Expr::Binary {
251 left: Box::new(self),
252 op: BinaryOp::Eq,
253 right: Box::new(other),
254 }
255 }
256
257 #[must_use]
259 pub fn ne(self, other: Expr) -> Self {
260 Expr::Binary {
261 left: Box::new(self),
262 op: BinaryOp::Ne,
263 right: Box::new(other),
264 }
265 }
266
267 #[must_use]
269 pub fn lt(self, other: Expr) -> Self {
270 Expr::Binary {
271 left: Box::new(self),
272 op: BinaryOp::Lt,
273 right: Box::new(other),
274 }
275 }
276
277 #[must_use]
279 pub fn le(self, other: Expr) -> Self {
280 Expr::Binary {
281 left: Box::new(self),
282 op: BinaryOp::Le,
283 right: Box::new(other),
284 }
285 }
286
287 #[must_use]
289 pub fn gt(self, other: Expr) -> Self {
290 Expr::Binary {
291 left: Box::new(self),
292 op: BinaryOp::Gt,
293 right: Box::new(other),
294 }
295 }
296
297 #[must_use]
299 pub fn ge(self, other: Expr) -> Self {
300 Expr::Binary {
301 left: Box::new(self),
302 op: BinaryOp::Ge,
303 right: Box::new(other),
304 }
305 }
306
307 #[must_use]
309 pub fn and(self, other: Expr) -> Self {
310 Expr::Binary {
311 left: Box::new(self),
312 op: BinaryOp::And,
313 right: Box::new(other),
314 }
315 }
316
317 #[must_use]
319 pub fn or(self, other: Expr) -> Self {
320 Expr::Binary {
321 left: Box::new(self),
322 op: BinaryOp::Or,
323 right: Box::new(other),
324 }
325 }
326
327 #[must_use]
329 pub fn like(self, pattern: Expr) -> Self {
330 Expr::Binary {
331 left: Box::new(self),
332 op: BinaryOp::Like,
333 right: Box::new(pattern),
334 }
335 }
336
337 #[must_use]
339 pub fn in_list(self, exprs: Vec<Expr>) -> Self {
340 Expr::Binary {
341 left: Box::new(self),
342 op: BinaryOp::In,
343 right: Box::new(Expr::List(exprs)),
344 }
345 }
346
347 #[must_use]
349 pub fn not_in_list(self, exprs: Vec<Expr>) -> Self {
350 Expr::Binary {
351 left: Box::new(self),
352 op: BinaryOp::NotIn,
353 right: Box::new(Expr::List(exprs)),
354 }
355 }
356
357 pub fn function_call(name: impl Into<String>, args: Vec<Expr>) -> Self {
359 Expr::FunctionCall {
360 name: name.into(),
361 args,
362 }
363 }
364
365 pub fn vector_distance(metric: crate::args::VectorMetric, left: Expr, right: Expr) -> Self {
367 let function = match metric {
368 crate::args::VectorMetric::L2 => VECTOR_L2_DISTANCE_FUNCTION,
369 crate::args::VectorMetric::InnerProduct => VECTOR_INNER_PRODUCT_FUNCTION,
370 crate::args::VectorMetric::Cosine => VECTOR_COSINE_DISTANCE_FUNCTION,
371 };
372 Expr::function_call(function, vec![left, right])
373 }
374
375 pub fn json_agg(expr: Expr) -> Self {
377 Expr::FunctionCall {
378 name: "json_agg".to_string(),
379 args: vec![expr],
380 }
381 }
382
383 pub fn json_build_object(pairs: Vec<(String, Expr)>) -> Self {
393 let args: Vec<Expr> = pairs
394 .into_iter()
395 .flat_map(|(key, value)| vec![Expr::Literal(LiteralSql::trusted(key)), value])
396 .collect();
397
398 Expr::FunctionCall {
399 name: "json_build_object".to_string(),
400 args,
401 }
402 }
403
404 pub fn coalesce(exprs: Vec<Expr>) -> Self {
406 Expr::FunctionCall {
407 name: "COALESCE".to_string(),
408 args: exprs,
409 }
410 }
411
412 #[must_use]
414 pub fn is_not_null(self) -> Self {
415 Expr::IsNotNull(Box::new(self))
416 }
417
418 #[must_use]
420 pub fn is_null(self) -> Self {
421 Expr::IsNull(Box::new(self))
422 }
423
424 #[must_use]
426 pub fn filter(self, predicate: Expr) -> Self {
427 Expr::Filter {
428 expr: Box::new(self),
429 predicate: Box::new(predicate),
430 }
431 }
432
433 pub fn exists(subquery: Select) -> Self {
435 Expr::Exists(Box::new(subquery))
436 }
437
438 pub fn not_exists(subquery: Select) -> Self {
440 Expr::NotExists(Box::new(subquery))
441 }
442
443 pub fn relation_some(
445 field: impl Into<String>,
446 parent_table: impl Into<String>,
447 target_table: impl Into<String>,
448 fk_db: impl Into<String>,
449 pk_db: impl Into<String>,
450 filter: Expr,
451 ) -> Self {
452 Expr::Relation {
453 op: RelationFilterOp::Some,
454 relation: Box::new(RelationFilter {
455 field: field.into(),
456 parent_table: parent_table.into(),
457 target_table: target_table.into(),
458 fk_db: fk_db.into(),
459 pk_db: pk_db.into(),
460 filter: Box::new(filter),
461 }),
462 }
463 }
464
465 pub fn relation_none(
467 field: impl Into<String>,
468 parent_table: impl Into<String>,
469 target_table: impl Into<String>,
470 fk_db: impl Into<String>,
471 pk_db: impl Into<String>,
472 filter: Expr,
473 ) -> Self {
474 Expr::Relation {
475 op: RelationFilterOp::None,
476 relation: Box::new(RelationFilter {
477 field: field.into(),
478 parent_table: parent_table.into(),
479 target_table: target_table.into(),
480 fk_db: fk_db.into(),
481 pk_db: pk_db.into(),
482 filter: Box::new(filter),
483 }),
484 }
485 }
486
487 pub fn relation_every(
489 field: impl Into<String>,
490 parent_table: impl Into<String>,
491 target_table: impl Into<String>,
492 fk_db: impl Into<String>,
493 pk_db: impl Into<String>,
494 filter: Expr,
495 ) -> Self {
496 Expr::Relation {
497 op: RelationFilterOp::Every,
498 relation: Box::new(RelationFilter {
499 field: field.into(),
500 parent_table: parent_table.into(),
501 target_table: target_table.into(),
502 fk_db: fk_db.into(),
503 pk_db: pk_db.into(),
504 filter: Box::new(filter),
505 }),
506 }
507 }
508
509 pub fn scalar_subquery(subquery: Select) -> Self {
513 Expr::ScalarSubquery(Box::new(subquery))
514 }
515
516 pub fn case_when(condition: Expr, then: Expr) -> Self {
518 Expr::CaseWhen {
519 condition: Box::new(condition),
520 then: Box::new(then),
521 }
522 }
523
524 pub fn star() -> Self {
526 Expr::Star
527 }
528}
529
530impl std::ops::Not for Expr {
532 type Output = Self;
533
534 fn not(self) -> Self::Output {
535 Expr::Not(Box::new(self))
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn test_column_expr() {
545 let expr = Expr::column("email");
546 match expr {
547 Expr::Column(name) => assert_eq!(name, "email"),
548 _ => panic!("Expected Column variant"),
549 }
550 }
551
552 #[test]
553 fn test_param_expr() {
554 let expr = Expr::param(42i64);
555 match expr {
556 Expr::Param(Value::I64(42)) => {}
557 _ => panic!("Expected Param with I64(42)"),
558 }
559 }
560
561 #[test]
562 fn test_binary_ops() {
563 let col = Expr::column("age");
564 let val = Expr::param(18i64);
565
566 let expr = col.ge(val);
567 match expr {
568 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::Ge),
569 _ => panic!("Expected Binary expression"),
570 }
571 }
572
573 #[test]
574 fn test_complex_expr() {
575 let expr = Expr::column("age")
576 .ge(Expr::param(18i64))
577 .and(Expr::column("email").like(Expr::param("%@gmail.com")));
578
579 match expr {
580 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::And),
581 _ => panic!("Expected Binary AND expression"),
582 }
583 }
584
585 #[test]
586 fn test_not_expr() {
587 let expr = !Expr::column("active").eq(Expr::param(true));
588 match expr {
589 Expr::Not(_) => {}
590 _ => panic!("Expected Not expression"),
591 }
592 }
593
594 #[test]
595 fn test_in_list() {
596 let expr = Expr::column("status").in_list(vec![
597 Expr::param(Value::String("active".to_string())),
598 Expr::param(Value::String("pending".to_string())),
599 ]);
600 match expr {
601 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::In),
602 _ => panic!("Expected Binary IN expression"),
603 }
604 }
605
606 #[test]
607 fn test_not_in_list() {
608 let expr = Expr::column("role").not_in_list(vec![
609 Expr::param(Value::String("admin".to_string())),
610 Expr::param(Value::String("superuser".to_string())),
611 ]);
612 match expr {
613 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::NotIn),
614 _ => panic!("Expected Binary NOT IN expression"),
615 }
616 }
617
618 #[test]
619 fn test_relation_predicate() {
620 let expr = Expr::relation_some(
621 "posts",
622 "users",
623 "posts",
624 "author_id",
625 "id",
626 Expr::column("posts__published").eq(Expr::param(true)),
627 );
628 match expr {
629 Expr::Relation { op, relation } => {
630 assert_eq!(op, RelationFilterOp::Some);
631 assert_eq!(relation.field, "posts");
632 assert_eq!(relation.parent_table, "users");
633 assert_eq!(relation.target_table, "posts");
634 assert_eq!(relation.fk_db, "author_id");
635 assert_eq!(relation.pk_db, "id");
636 }
637 _ => panic!("Expected relation predicate"),
638 }
639 }
640}