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)]
76pub enum Expr {
77 Column(String),
79 Param(Value),
81 Binary {
83 left: Box<Expr>,
85 op: BinaryOp,
87 right: Box<Expr>,
89 },
90 Not(Box<Expr>),
92 FunctionCall {
94 name: String,
96 args: Vec<Expr>,
98 },
99 Filter {
101 expr: Box<Expr>,
103 predicate: Box<Expr>,
105 },
106 Exists(Box<Select>),
108 NotExists(Box<Select>),
110 Relation {
112 op: RelationFilterOp,
114 relation: Box<RelationFilter>,
116 },
117 ScalarSubquery(Box<Select>),
123 IsNull(Box<Expr>),
125 IsNotNull(Box<Expr>),
127 Literal(String),
133 List(Vec<Expr>),
138 CaseWhen {
140 condition: Box<Expr>,
142 then: Box<Expr>,
144 },
145 Star,
147}
148
149impl Expr {
150 pub fn column(name: impl Into<String>) -> Self {
152 Expr::Column(name.into())
153 }
154
155 pub fn param(value: impl Into<Value>) -> Self {
157 Expr::Param(value.into())
158 }
159
160 #[must_use]
162 pub fn eq(self, other: Expr) -> Self {
163 Expr::Binary {
164 left: Box::new(self),
165 op: BinaryOp::Eq,
166 right: Box::new(other),
167 }
168 }
169
170 #[must_use]
172 pub fn ne(self, other: Expr) -> Self {
173 Expr::Binary {
174 left: Box::new(self),
175 op: BinaryOp::Ne,
176 right: Box::new(other),
177 }
178 }
179
180 #[must_use]
182 pub fn lt(self, other: Expr) -> Self {
183 Expr::Binary {
184 left: Box::new(self),
185 op: BinaryOp::Lt,
186 right: Box::new(other),
187 }
188 }
189
190 #[must_use]
192 pub fn le(self, other: Expr) -> Self {
193 Expr::Binary {
194 left: Box::new(self),
195 op: BinaryOp::Le,
196 right: Box::new(other),
197 }
198 }
199
200 #[must_use]
202 pub fn gt(self, other: Expr) -> Self {
203 Expr::Binary {
204 left: Box::new(self),
205 op: BinaryOp::Gt,
206 right: Box::new(other),
207 }
208 }
209
210 #[must_use]
212 pub fn ge(self, other: Expr) -> Self {
213 Expr::Binary {
214 left: Box::new(self),
215 op: BinaryOp::Ge,
216 right: Box::new(other),
217 }
218 }
219
220 #[must_use]
222 pub fn and(self, other: Expr) -> Self {
223 Expr::Binary {
224 left: Box::new(self),
225 op: BinaryOp::And,
226 right: Box::new(other),
227 }
228 }
229
230 #[must_use]
232 pub fn or(self, other: Expr) -> Self {
233 Expr::Binary {
234 left: Box::new(self),
235 op: BinaryOp::Or,
236 right: Box::new(other),
237 }
238 }
239
240 #[must_use]
242 pub fn like(self, pattern: Expr) -> Self {
243 Expr::Binary {
244 left: Box::new(self),
245 op: BinaryOp::Like,
246 right: Box::new(pattern),
247 }
248 }
249
250 #[must_use]
252 pub fn in_list(self, exprs: Vec<Expr>) -> Self {
253 Expr::Binary {
254 left: Box::new(self),
255 op: BinaryOp::In,
256 right: Box::new(Expr::List(exprs)),
257 }
258 }
259
260 #[must_use]
262 pub fn not_in_list(self, exprs: Vec<Expr>) -> Self {
263 Expr::Binary {
264 left: Box::new(self),
265 op: BinaryOp::NotIn,
266 right: Box::new(Expr::List(exprs)),
267 }
268 }
269
270 pub fn function_call(name: impl Into<String>, args: Vec<Expr>) -> Self {
272 Expr::FunctionCall {
273 name: name.into(),
274 args,
275 }
276 }
277
278 pub fn vector_distance(metric: crate::args::VectorMetric, left: Expr, right: Expr) -> Self {
280 let function = match metric {
281 crate::args::VectorMetric::L2 => VECTOR_L2_DISTANCE_FUNCTION,
282 crate::args::VectorMetric::InnerProduct => VECTOR_INNER_PRODUCT_FUNCTION,
283 crate::args::VectorMetric::Cosine => VECTOR_COSINE_DISTANCE_FUNCTION,
284 };
285 Expr::function_call(function, vec![left, right])
286 }
287
288 pub fn json_agg(expr: Expr) -> Self {
290 Expr::FunctionCall {
291 name: "json_agg".to_string(),
292 args: vec![expr],
293 }
294 }
295
296 pub fn json_build_object(pairs: Vec<(String, Expr)>) -> Self {
306 let args: Vec<Expr> = pairs
307 .into_iter()
308 .flat_map(|(key, value)| vec![Expr::Literal(key), value])
309 .collect();
310
311 Expr::FunctionCall {
312 name: "json_build_object".to_string(),
313 args,
314 }
315 }
316
317 pub fn coalesce(exprs: Vec<Expr>) -> Self {
319 Expr::FunctionCall {
320 name: "COALESCE".to_string(),
321 args: exprs,
322 }
323 }
324
325 #[must_use]
327 pub fn is_not_null(self) -> Self {
328 Expr::IsNotNull(Box::new(self))
329 }
330
331 #[must_use]
333 pub fn is_null(self) -> Self {
334 Expr::IsNull(Box::new(self))
335 }
336
337 #[must_use]
339 pub fn filter(self, predicate: Expr) -> Self {
340 Expr::Filter {
341 expr: Box::new(self),
342 predicate: Box::new(predicate),
343 }
344 }
345
346 pub fn exists(subquery: Select) -> Self {
348 Expr::Exists(Box::new(subquery))
349 }
350
351 pub fn not_exists(subquery: Select) -> Self {
353 Expr::NotExists(Box::new(subquery))
354 }
355
356 pub fn relation_some(
358 field: impl Into<String>,
359 parent_table: impl Into<String>,
360 target_table: impl Into<String>,
361 fk_db: impl Into<String>,
362 pk_db: impl Into<String>,
363 filter: Expr,
364 ) -> Self {
365 Expr::Relation {
366 op: RelationFilterOp::Some,
367 relation: Box::new(RelationFilter {
368 field: field.into(),
369 parent_table: parent_table.into(),
370 target_table: target_table.into(),
371 fk_db: fk_db.into(),
372 pk_db: pk_db.into(),
373 filter: Box::new(filter),
374 }),
375 }
376 }
377
378 pub fn relation_none(
380 field: impl Into<String>,
381 parent_table: impl Into<String>,
382 target_table: impl Into<String>,
383 fk_db: impl Into<String>,
384 pk_db: impl Into<String>,
385 filter: Expr,
386 ) -> Self {
387 Expr::Relation {
388 op: RelationFilterOp::None,
389 relation: Box::new(RelationFilter {
390 field: field.into(),
391 parent_table: parent_table.into(),
392 target_table: target_table.into(),
393 fk_db: fk_db.into(),
394 pk_db: pk_db.into(),
395 filter: Box::new(filter),
396 }),
397 }
398 }
399
400 pub fn relation_every(
402 field: impl Into<String>,
403 parent_table: impl Into<String>,
404 target_table: impl Into<String>,
405 fk_db: impl Into<String>,
406 pk_db: impl Into<String>,
407 filter: Expr,
408 ) -> Self {
409 Expr::Relation {
410 op: RelationFilterOp::Every,
411 relation: Box::new(RelationFilter {
412 field: field.into(),
413 parent_table: parent_table.into(),
414 target_table: target_table.into(),
415 fk_db: fk_db.into(),
416 pk_db: pk_db.into(),
417 filter: Box::new(filter),
418 }),
419 }
420 }
421
422 pub fn scalar_subquery(subquery: Select) -> Self {
426 Expr::ScalarSubquery(Box::new(subquery))
427 }
428
429 pub fn case_when(condition: Expr, then: Expr) -> Self {
431 Expr::CaseWhen {
432 condition: Box::new(condition),
433 then: Box::new(then),
434 }
435 }
436
437 pub fn star() -> Self {
439 Expr::Star
440 }
441}
442
443impl std::ops::Not for Expr {
445 type Output = Self;
446
447 fn not(self) -> Self::Output {
448 Expr::Not(Box::new(self))
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn test_column_expr() {
458 let expr = Expr::column("email");
459 match expr {
460 Expr::Column(name) => assert_eq!(name, "email"),
461 _ => panic!("Expected Column variant"),
462 }
463 }
464
465 #[test]
466 fn test_param_expr() {
467 let expr = Expr::param(42i64);
468 match expr {
469 Expr::Param(Value::I64(42)) => {}
470 _ => panic!("Expected Param with I64(42)"),
471 }
472 }
473
474 #[test]
475 fn test_binary_ops() {
476 let col = Expr::column("age");
477 let val = Expr::param(18i64);
478
479 let expr = col.ge(val);
480 match expr {
481 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::Ge),
482 _ => panic!("Expected Binary expression"),
483 }
484 }
485
486 #[test]
487 fn test_complex_expr() {
488 let expr = Expr::column("age")
489 .ge(Expr::param(18i64))
490 .and(Expr::column("email").like(Expr::param("%@gmail.com")));
491
492 match expr {
493 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::And),
494 _ => panic!("Expected Binary AND expression"),
495 }
496 }
497
498 #[test]
499 fn test_not_expr() {
500 let expr = !Expr::column("active").eq(Expr::param(true));
501 match expr {
502 Expr::Not(_) => {}
503 _ => panic!("Expected Not expression"),
504 }
505 }
506
507 #[test]
508 fn test_in_list() {
509 let expr = Expr::column("status").in_list(vec![
510 Expr::param(Value::String("active".to_string())),
511 Expr::param(Value::String("pending".to_string())),
512 ]);
513 match expr {
514 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::In),
515 _ => panic!("Expected Binary IN expression"),
516 }
517 }
518
519 #[test]
520 fn test_not_in_list() {
521 let expr = Expr::column("role").not_in_list(vec![
522 Expr::param(Value::String("admin".to_string())),
523 Expr::param(Value::String("superuser".to_string())),
524 ]);
525 match expr {
526 Expr::Binary { op, .. } => assert_eq!(op, BinaryOp::NotIn),
527 _ => panic!("Expected Binary NOT IN expression"),
528 }
529 }
530
531 #[test]
532 fn test_relation_predicate() {
533 let expr = Expr::relation_some(
534 "posts",
535 "users",
536 "posts",
537 "author_id",
538 "id",
539 Expr::column("posts__published").eq(Expr::param(true)),
540 );
541 match expr {
542 Expr::Relation { op, relation } => {
543 assert_eq!(op, RelationFilterOp::Some);
544 assert_eq!(relation.field, "posts");
545 assert_eq!(relation.parent_table, "users");
546 assert_eq!(relation.target_table, "posts");
547 assert_eq!(relation.fk_db, "author_id");
548 assert_eq!(relation.pk_db, "id");
549 }
550 _ => panic!("Expected relation predicate"),
551 }
552 }
553}