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)]
15pub enum RelationFilterOp {
16 Some,
18 None,
20 Every,
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub struct RelationFilter {
27 pub field: String,
29 pub parent_table: String,
31 pub target_table: String,
33 pub fk_db: String,
35 pub pk_db: String,
37 pub filter: Box<Expr>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum BinaryOp {
44 Eq,
46 Ne,
48 Lt,
50 Le,
52 Gt,
54 Ge,
56 And,
58 Or,
60 Like,
62 ArrayContains,
64 ArrayContainedBy,
66 ArrayOverlaps,
68 In,
70 NotIn,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct LiteralSql(String);
85
86impl LiteralSql {
87 #[must_use]
90 pub fn from_static(text: &'static str) -> Self {
91 Self(text.to_string())
92 }
93
94 #[must_use]
98 pub fn trusted(text: impl Into<String>) -> Self {
99 Self(text.into())
100 }
101
102 #[must_use]
104 pub fn as_str(&self) -> &str {
105 &self.0
106 }
107}
108
109#[derive(Debug, Clone, PartialEq)]
111pub enum Expr {
112 Column(String),
114 Param(Value),
116 Binary {
118 left: Box<Expr>,
120 op: BinaryOp,
122 right: Box<Expr>,
124 },
125 Not(Box<Expr>),
127 FunctionCall {
129 name: String,
131 args: Vec<Expr>,
133 },
134 Filter {
136 expr: Box<Expr>,
138 predicate: Box<Expr>,
140 },
141 Exists(Box<Select>),
143 NotExists(Box<Select>),
145 Relation {
147 op: RelationFilterOp,
149 relation: Box<RelationFilter>,
151 },
152 ScalarSubquery(Box<Select>),
158 IsNull(Box<Expr>),
160 IsNotNull(Box<Expr>),
162 Literal(LiteralSql),
169 List(Vec<Expr>),
174 CaseWhen {
176 condition: Box<Expr>,
178 then: Box<Expr>,
180 },
181 Star,
183}
184
185impl Expr {
186 pub fn column(name: impl Into<String>) -> Self {
188 Expr::Column(name.into())
189 }
190
191 pub fn param(value: impl Into<Value>) -> Self {
193 Expr::Param(value.into())
194 }
195
196 #[must_use]
198 pub fn eq(self, other: Expr) -> Self {
199 Expr::Binary {
200 left: Box::new(self),
201 op: BinaryOp::Eq,
202 right: Box::new(other),
203 }
204 }
205
206 #[must_use]
208 pub fn ne(self, other: Expr) -> Self {
209 Expr::Binary {
210 left: Box::new(self),
211 op: BinaryOp::Ne,
212 right: Box::new(other),
213 }
214 }
215
216 #[must_use]
218 pub fn lt(self, other: Expr) -> Self {
219 Expr::Binary {
220 left: Box::new(self),
221 op: BinaryOp::Lt,
222 right: Box::new(other),
223 }
224 }
225
226 #[must_use]
228 pub fn le(self, other: Expr) -> Self {
229 Expr::Binary {
230 left: Box::new(self),
231 op: BinaryOp::Le,
232 right: Box::new(other),
233 }
234 }
235
236 #[must_use]
238 pub fn gt(self, other: Expr) -> Self {
239 Expr::Binary {
240 left: Box::new(self),
241 op: BinaryOp::Gt,
242 right: Box::new(other),
243 }
244 }
245
246 #[must_use]
248 pub fn ge(self, other: Expr) -> Self {
249 Expr::Binary {
250 left: Box::new(self),
251 op: BinaryOp::Ge,
252 right: Box::new(other),
253 }
254 }
255
256 #[must_use]
258 pub fn and(self, other: Expr) -> Self {
259 Expr::Binary {
260 left: Box::new(self),
261 op: BinaryOp::And,
262 right: Box::new(other),
263 }
264 }
265
266 #[must_use]
268 pub fn or(self, other: Expr) -> Self {
269 Expr::Binary {
270 left: Box::new(self),
271 op: BinaryOp::Or,
272 right: Box::new(other),
273 }
274 }
275
276 #[must_use]
278 pub fn like(self, pattern: Expr) -> Self {
279 Expr::Binary {
280 left: Box::new(self),
281 op: BinaryOp::Like,
282 right: Box::new(pattern),
283 }
284 }
285
286 #[must_use]
288 pub fn in_list(self, exprs: Vec<Expr>) -> Self {
289 Expr::Binary {
290 left: Box::new(self),
291 op: BinaryOp::In,
292 right: Box::new(Expr::List(exprs)),
293 }
294 }
295
296 #[must_use]
298 pub fn not_in_list(self, exprs: Vec<Expr>) -> Self {
299 Expr::Binary {
300 left: Box::new(self),
301 op: BinaryOp::NotIn,
302 right: Box::new(Expr::List(exprs)),
303 }
304 }
305
306 pub fn function_call(name: impl Into<String>, args: Vec<Expr>) -> Self {
308 Expr::FunctionCall {
309 name: name.into(),
310 args,
311 }
312 }
313
314 pub fn vector_distance(metric: crate::args::VectorMetric, left: Expr, right: Expr) -> Self {
316 let function = match metric {
317 crate::args::VectorMetric::L2 => VECTOR_L2_DISTANCE_FUNCTION,
318 crate::args::VectorMetric::InnerProduct => VECTOR_INNER_PRODUCT_FUNCTION,
319 crate::args::VectorMetric::Cosine => VECTOR_COSINE_DISTANCE_FUNCTION,
320 };
321 Expr::function_call(function, vec![left, right])
322 }
323
324 pub fn json_agg(expr: Expr) -> Self {
326 Expr::FunctionCall {
327 name: "json_agg".to_string(),
328 args: vec![expr],
329 }
330 }
331
332 pub fn json_build_object(pairs: Vec<(String, Expr)>) -> Self {
342 let args: Vec<Expr> = pairs
343 .into_iter()
344 .flat_map(|(key, value)| vec![Expr::Literal(LiteralSql::trusted(key)), value])
345 .collect();
346
347 Expr::FunctionCall {
348 name: "json_build_object".to_string(),
349 args,
350 }
351 }
352
353 pub fn coalesce(exprs: Vec<Expr>) -> Self {
355 Expr::FunctionCall {
356 name: "COALESCE".to_string(),
357 args: exprs,
358 }
359 }
360
361 #[must_use]
363 pub fn is_not_null(self) -> Self {
364 Expr::IsNotNull(Box::new(self))
365 }
366
367 #[must_use]
369 pub fn is_null(self) -> Self {
370 Expr::IsNull(Box::new(self))
371 }
372
373 #[must_use]
375 pub fn filter(self, predicate: Expr) -> Self {
376 Expr::Filter {
377 expr: Box::new(self),
378 predicate: Box::new(predicate),
379 }
380 }
381
382 pub fn exists(subquery: Select) -> Self {
384 Expr::Exists(Box::new(subquery))
385 }
386
387 pub fn not_exists(subquery: Select) -> Self {
389 Expr::NotExists(Box::new(subquery))
390 }
391
392 pub fn relation_some(
394 field: impl Into<String>,
395 parent_table: impl Into<String>,
396 target_table: impl Into<String>,
397 fk_db: impl Into<String>,
398 pk_db: impl Into<String>,
399 filter: Expr,
400 ) -> Self {
401 Expr::Relation {
402 op: RelationFilterOp::Some,
403 relation: Box::new(RelationFilter {
404 field: field.into(),
405 parent_table: parent_table.into(),
406 target_table: target_table.into(),
407 fk_db: fk_db.into(),
408 pk_db: pk_db.into(),
409 filter: Box::new(filter),
410 }),
411 }
412 }
413
414 pub fn relation_none(
416 field: impl Into<String>,
417 parent_table: impl Into<String>,
418 target_table: impl Into<String>,
419 fk_db: impl Into<String>,
420 pk_db: impl Into<String>,
421 filter: Expr,
422 ) -> Self {
423 Expr::Relation {
424 op: RelationFilterOp::None,
425 relation: Box::new(RelationFilter {
426 field: field.into(),
427 parent_table: parent_table.into(),
428 target_table: target_table.into(),
429 fk_db: fk_db.into(),
430 pk_db: pk_db.into(),
431 filter: Box::new(filter),
432 }),
433 }
434 }
435
436 pub fn relation_every(
438 field: impl Into<String>,
439 parent_table: impl Into<String>,
440 target_table: impl Into<String>,
441 fk_db: impl Into<String>,
442 pk_db: impl Into<String>,
443 filter: Expr,
444 ) -> Self {
445 Expr::Relation {
446 op: RelationFilterOp::Every,
447 relation: Box::new(RelationFilter {
448 field: field.into(),
449 parent_table: parent_table.into(),
450 target_table: target_table.into(),
451 fk_db: fk_db.into(),
452 pk_db: pk_db.into(),
453 filter: Box::new(filter),
454 }),
455 }
456 }
457
458 pub fn scalar_subquery(subquery: Select) -> Self {
462 Expr::ScalarSubquery(Box::new(subquery))
463 }
464
465 pub fn case_when(condition: Expr, then: Expr) -> Self {
467 Expr::CaseWhen {
468 condition: Box::new(condition),
469 then: Box::new(then),
470 }
471 }
472
473 pub fn star() -> Self {
475 Expr::Star
476 }
477}
478
479impl std::ops::Not for Expr {
481 type Output = Self;
482
483 fn not(self) -> Self::Output {
484 Expr::Not(Box::new(self))
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_column_expr() {
494 let expr = Expr::column("email");
495 match expr {
496 Expr::Column(name) => assert_eq!(name, "email"),
497 _ => panic!("Expected Column variant"),
498 }
499 }
500
501 #[test]
502 fn test_param_expr() {
503 let expr = Expr::param(42i64);
504 match expr {
505 Expr::Param(Value::I64(42)) => {}
506 _ => panic!("Expected Param with I64(42)"),
507 }
508 }
509
510 #[test]
511 fn test_binary_ops() {
512 let col = Expr::column("age");
513 let val = Expr::param(18i64);
514
515 let expr = col.ge(val);
516 match expr {
517 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::Ge),
518 _ => panic!("Expected Binary expression"),
519 }
520 }
521
522 #[test]
523 fn test_complex_expr() {
524 let expr = Expr::column("age")
525 .ge(Expr::param(18i64))
526 .and(Expr::column("email").like(Expr::param("%@gmail.com")));
527
528 match expr {
529 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::And),
530 _ => panic!("Expected Binary AND expression"),
531 }
532 }
533
534 #[test]
535 fn test_not_expr() {
536 let expr = !Expr::column("active").eq(Expr::param(true));
537 match expr {
538 Expr::Not(_) => {}
539 _ => panic!("Expected Not expression"),
540 }
541 }
542
543 #[test]
544 fn test_in_list() {
545 let expr = Expr::column("status").in_list(vec![
546 Expr::param(Value::String("active".to_string())),
547 Expr::param(Value::String("pending".to_string())),
548 ]);
549 match expr {
550 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::In),
551 _ => panic!("Expected Binary IN expression"),
552 }
553 }
554
555 #[test]
556 fn test_not_in_list() {
557 let expr = Expr::column("role").not_in_list(vec![
558 Expr::param(Value::String("admin".to_string())),
559 Expr::param(Value::String("superuser".to_string())),
560 ]);
561 match expr {
562 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::NotIn),
563 _ => panic!("Expected Binary NOT IN expression"),
564 }
565 }
566
567 #[test]
568 fn test_relation_predicate() {
569 let expr = Expr::relation_some(
570 "posts",
571 "users",
572 "posts",
573 "author_id",
574 "id",
575 Expr::column("posts__published").eq(Expr::param(true)),
576 );
577 match expr {
578 Expr::Relation { op, relation } => {
579 assert_eq!(op, RelationFilterOp::Some);
580 assert_eq!(relation.field, "posts");
581 assert_eq!(relation.parent_table, "users");
582 assert_eq!(relation.target_table, "posts");
583 assert_eq!(relation.fk_db, "author_id");
584 assert_eq!(relation.pk_db, "id");
585 }
586 _ => panic!("Expected relation predicate"),
587 }
588 }
589}