1use super::{Field, Value, WhereOperator};
20use crate::Result;
21use std::collections::HashMap;
22
23fn infer_type_cast(value: &Value) -> &'static str {
27 match value {
28 Value::String(_) => "::text",
29 Value::Number(_) => "::numeric", Value::Bool(_) => "::boolean",
31 Value::Null => "", Value::Array(_) => "", Value::FloatArray(_) => "", Value::RawSql(_) => "", }
36}
37
38pub fn generate_where_operator_sql(
61 operator: &WhereOperator,
62 param_index: &mut usize,
63 params: &mut HashMap<usize, Value>,
64) -> Result<String> {
65 operator.validate().map_err(crate::Error::InvalidSchema)?;
66
67 match operator {
68 WhereOperator::Eq(field, value) => {
72 let field_sql = field.to_sql();
73 if value.is_null() {
74 Ok(format!("{} IS NULL", field_sql))
75 } else {
76 let param_num = *param_index + 1;
77 *param_index += 1;
78 params.insert(param_num, value.clone());
79 let cast = match field {
81 Field::JsonbField(_) | Field::JsonbPath(_) => infer_type_cast(value),
82 Field::DirectColumn(_) => "", };
84 Ok(format!("{}{} = ${}", field_sql, cast, param_num))
85 }
86 }
87
88 WhereOperator::Neq(field, value) => {
89 let field_sql = field.to_sql();
90 if value.is_null() {
91 Ok(format!("{} IS NOT NULL", field_sql))
92 } else {
93 let param_num = *param_index + 1;
94 *param_index += 1;
95 params.insert(param_num, value.clone());
96 let cast = match field {
97 Field::JsonbField(_) | Field::JsonbPath(_) => infer_type_cast(value),
98 Field::DirectColumn(_) => "",
99 };
100 Ok(format!("{}{} != ${}", field_sql, cast, param_num))
101 }
102 }
103
104 WhereOperator::Gt(field, value) => {
105 let field_sql = field.to_sql();
106 let param_num = *param_index + 1;
107 *param_index += 1;
108 params.insert(param_num, value.clone());
109 let cast = match field {
110 Field::JsonbField(_) | Field::JsonbPath(_) => infer_type_cast(value),
111 Field::DirectColumn(_) => "",
112 };
113 Ok(format!("{}{} > ${}", field_sql, cast, param_num))
114 }
115
116 WhereOperator::Gte(field, value) => {
117 let field_sql = field.to_sql();
118 let param_num = *param_index + 1;
119 *param_index += 1;
120 params.insert(param_num, value.clone());
121 let cast = match field {
122 Field::JsonbField(_) | Field::JsonbPath(_) => infer_type_cast(value),
123 Field::DirectColumn(_) => "",
124 };
125 Ok(format!("{}{} >= ${}", field_sql, cast, param_num))
126 }
127
128 WhereOperator::Lt(field, value) => {
129 let field_sql = field.to_sql();
130 let param_num = *param_index + 1;
131 *param_index += 1;
132 params.insert(param_num, value.clone());
133 let cast = match field {
134 Field::JsonbField(_) | Field::JsonbPath(_) => infer_type_cast(value),
135 Field::DirectColumn(_) => "",
136 };
137 Ok(format!("{}{} < ${}", field_sql, cast, param_num))
138 }
139
140 WhereOperator::Lte(field, value) => {
141 let field_sql = field.to_sql();
142 let param_num = *param_index + 1;
143 *param_index += 1;
144 params.insert(param_num, value.clone());
145 let cast = match field {
146 Field::JsonbField(_) | Field::JsonbPath(_) => infer_type_cast(value),
147 Field::DirectColumn(_) => "",
148 };
149 Ok(format!("{}{} <= ${}", field_sql, cast, param_num))
150 }
151
152 WhereOperator::In(field, values) => {
154 let field_sql = field.to_sql();
155 let placeholders: Vec<String> = values
156 .iter()
157 .map(|v| {
158 let param_num = *param_index + 1;
159 *param_index += 1;
160 params.insert(param_num, v.clone());
161 format!("${}", param_num)
162 })
163 .collect();
164 Ok(format!("{} IN ({})", field_sql, placeholders.join(", ")))
165 }
166
167 WhereOperator::Nin(field, values) => {
168 let field_sql = field.to_sql();
169 let placeholders: Vec<String> = values
170 .iter()
171 .map(|v| {
172 let param_num = *param_index + 1;
173 *param_index += 1;
174 params.insert(param_num, v.clone());
175 format!("${}", param_num)
176 })
177 .collect();
178 Ok(format!(
179 "{} NOT IN ({})",
180 field_sql,
181 placeholders.join(", ")
182 ))
183 }
184
185 WhereOperator::Contains(field, substring) => {
186 let field_sql = field.to_sql();
187 let param_num = *param_index + 1;
188 *param_index += 1;
189 params.insert(param_num, Value::String(substring.clone()));
190 Ok(format!(
191 "{} LIKE '%' || ${}::text || '%'",
192 field_sql, param_num
193 ))
194 }
195
196 WhereOperator::ArrayContains(field, value) => {
197 let field_sql = field.to_sql();
198 let param_num = *param_index + 1;
199 *param_index += 1;
200 params.insert(param_num, value.clone());
201 Ok(format!("{} @> ARRAY[${}]", field_sql, param_num))
202 }
203
204 WhereOperator::ArrayContainedBy(field, value) => {
205 let field_sql = field.to_sql();
206 let param_num = *param_index + 1;
207 *param_index += 1;
208 params.insert(param_num, value.clone());
209 Ok(format!("{} <@ ARRAY[${}]", field_sql, param_num))
210 }
211
212 WhereOperator::ArrayOverlaps(field, values) => {
213 let field_sql = field.to_sql();
214 let placeholders: Vec<String> = values
215 .iter()
216 .map(|v| {
217 let param_num = *param_index + 1;
218 *param_index += 1;
219 params.insert(param_num, v.clone());
220 format!("${}", param_num)
221 })
222 .collect();
223 Ok(format!(
224 "{} && ARRAY[{}]",
225 field_sql,
226 placeholders.join(", ")
227 ))
228 }
229
230 WhereOperator::LenEq(field, len) => {
232 let field_sql = field.to_sql();
233 Ok(format!("array_length({}, 1) = {}", field_sql, len))
234 }
235
236 WhereOperator::LenGt(field, len) => {
237 let field_sql = field.to_sql();
238 Ok(format!("array_length({}, 1) > {}", field_sql, len))
239 }
240
241 WhereOperator::LenGte(field, len) => {
242 let field_sql = field.to_sql();
243 Ok(format!("array_length({}, 1) >= {}", field_sql, len))
244 }
245
246 WhereOperator::LenLt(field, len) => {
247 let field_sql = field.to_sql();
248 Ok(format!("array_length({}, 1) < {}", field_sql, len))
249 }
250
251 WhereOperator::LenLte(field, len) => {
252 let field_sql = field.to_sql();
253 Ok(format!("array_length({}, 1) <= {}", field_sql, len))
254 }
255
256 WhereOperator::Icontains(field, substring) => {
258 let field_sql = field.to_sql();
259 let param_num = *param_index + 1;
260 *param_index += 1;
261 params.insert(param_num, Value::String(substring.clone()));
262 Ok(format!(
263 "{} ILIKE '%' || ${}::text || '%'",
264 field_sql, param_num
265 ))
266 }
267
268 WhereOperator::Startswith(field, prefix) => {
269 let field_sql = field.to_sql();
270 let param_num = *param_index + 1;
271 *param_index += 1;
272 params.insert(param_num, Value::String(format!("{}%", prefix)));
273 Ok(format!("{} LIKE ${}", field_sql, param_num))
274 }
275
276 WhereOperator::Istartswith(field, prefix) => {
277 let field_sql = field.to_sql();
278 let param_num = *param_index + 1;
279 *param_index += 1;
280 params.insert(param_num, Value::String(format!("{}%", prefix)));
281 Ok(format!("{} ILIKE ${}", field_sql, param_num))
282 }
283
284 WhereOperator::Endswith(field, suffix) => {
285 let field_sql = field.to_sql();
286 let param_num = *param_index + 1;
287 *param_index += 1;
288 params.insert(param_num, Value::String(format!("%{}", suffix)));
289 Ok(format!("{} LIKE ${}", field_sql, param_num))
290 }
291
292 WhereOperator::Iendswith(field, suffix) => {
293 let field_sql = field.to_sql();
294 let param_num = *param_index + 1;
295 *param_index += 1;
296 params.insert(param_num, Value::String(format!("%{}", suffix)));
297 Ok(format!("{} ILIKE ${}", field_sql, param_num))
298 }
299
300 WhereOperator::Like(field, pattern) => {
301 let field_sql = field.to_sql();
302 let param_num = *param_index + 1;
303 *param_index += 1;
304 params.insert(param_num, Value::String(pattern.clone()));
305 Ok(format!("{} LIKE ${}", field_sql, param_num))
306 }
307
308 WhereOperator::Ilike(field, pattern) => {
309 let field_sql = field.to_sql();
310 let param_num = *param_index + 1;
311 *param_index += 1;
312 params.insert(param_num, Value::String(pattern.clone()));
313 Ok(format!("{} ILIKE ${}", field_sql, param_num))
314 }
315
316 WhereOperator::IsNull(field, is_null) => {
318 let field_sql = field.to_sql();
319 if *is_null {
320 Ok(format!("{} IS NULL", field_sql))
321 } else {
322 Ok(format!("{} IS NOT NULL", field_sql))
323 }
324 }
325
326 WhereOperator::L2Distance {
328 field,
329 vector,
330 threshold,
331 } => {
332 let field_sql = field.to_sql();
333 let param_num = *param_index + 1;
334 *param_index += 1;
335 params.insert(param_num, Value::FloatArray(vector.clone()));
336 Ok(format!(
337 "l2_distance({}::vector, ${}::vector) < {}",
338 field_sql, param_num, threshold
339 ))
340 }
341
342 WhereOperator::CosineDistance {
343 field,
344 vector,
345 threshold,
346 } => {
347 let field_sql = field.to_sql();
348 let param_num = *param_index + 1;
349 *param_index += 1;
350 params.insert(param_num, Value::FloatArray(vector.clone()));
351 Ok(format!(
352 "cosine_distance({}::vector, ${}::vector) < {}",
353 field_sql, param_num, threshold
354 ))
355 }
356
357 WhereOperator::InnerProduct {
358 field,
359 vector,
360 threshold,
361 } => {
362 let field_sql = field.to_sql();
363 let param_num = *param_index + 1;
364 *param_index += 1;
365 params.insert(param_num, Value::FloatArray(vector.clone()));
366 Ok(format!(
367 "inner_product({}::vector, ${}::vector) > {}",
368 field_sql, param_num, threshold
369 ))
370 }
371
372 WhereOperator::L1Distance {
373 field,
374 vector,
375 threshold,
376 } => {
377 let field_sql = field.to_sql();
378 let param_num = *param_index + 1;
379 *param_index += 1;
380 params.insert(param_num, Value::FloatArray(vector.clone()));
381 Ok(format!(
382 "l1_distance({}::vector, ${}::vector) < {}",
383 field_sql, param_num, threshold
384 ))
385 }
386
387 WhereOperator::HammingDistance {
388 field,
389 vector,
390 threshold,
391 } => {
392 let field_sql = field.to_sql();
393 let param_num = *param_index + 1;
394 *param_index += 1;
395 params.insert(param_num, Value::FloatArray(vector.clone()));
396 Ok(format!(
397 "hamming_distance({}::bit, ${}::bit) < {}",
398 field_sql, param_num, threshold
399 ))
400 }
401
402 WhereOperator::JaccardDistance {
403 field,
404 set,
405 threshold,
406 } => {
407 let field_sql = field.to_sql();
408 let param_num = *param_index + 1;
409 *param_index += 1;
410 let value_array: Vec<Value> = set.iter().map(|s| Value::String(s.clone())).collect();
411 params.insert(param_num, Value::Array(value_array));
412 Ok(format!(
413 "jaccard_distance({}::text[], ${}::text[]) < {}",
414 field_sql, param_num, threshold
415 ))
416 }
417
418 WhereOperator::Matches {
420 field,
421 query,
422 language,
423 } => {
424 let field_sql = field.to_sql();
425 let param_num = *param_index + 1;
426 *param_index += 1;
427 params.insert(param_num, Value::String(query.clone()));
428 let lang = language.as_deref().unwrap_or("english");
429 Ok(format!(
430 "{} @@ plainto_tsquery('{}', ${})",
431 field_sql, lang, param_num
432 ))
433 }
434
435 WhereOperator::PlainQuery { field, query } => {
436 let field_sql = field.to_sql();
437 let param_num = *param_index + 1;
438 *param_index += 1;
439 params.insert(param_num, Value::String(query.clone()));
440 Ok(format!(
441 "{} @@ plainto_tsquery(${})::tsvector",
442 field_sql, param_num
443 ))
444 }
445
446 WhereOperator::PhraseQuery {
447 field,
448 query,
449 language,
450 } => {
451 let field_sql = field.to_sql();
452 let param_num = *param_index + 1;
453 *param_index += 1;
454 params.insert(param_num, Value::String(query.clone()));
455 let lang = language.as_deref().unwrap_or("english");
456 Ok(format!(
457 "{} @@ phraseto_tsquery('{}', ${})",
458 field_sql, lang, param_num
459 ))
460 }
461
462 WhereOperator::WebsearchQuery {
463 field,
464 query,
465 language,
466 } => {
467 let field_sql = field.to_sql();
468 let param_num = *param_index + 1;
469 *param_index += 1;
470 params.insert(param_num, Value::String(query.clone()));
471 let lang = language.as_deref().unwrap_or("english");
472 Ok(format!(
473 "{} @@ websearch_to_tsquery('{}', ${})",
474 field_sql, lang, param_num
475 ))
476 }
477
478 WhereOperator::IsIPv4(field) => {
480 let field_sql = field.to_sql();
481 Ok(format!("family({}::inet) = 4", field_sql))
482 }
483
484 WhereOperator::IsIPv6(field) => {
485 let field_sql = field.to_sql();
486 Ok(format!("family({}::inet) = 6", field_sql))
487 }
488
489 WhereOperator::IsPrivate(field) => {
490 let field_sql = field.to_sql();
491 Ok(format!(
493 "({}::inet << '10.0.0.0/8'::inet OR {}::inet << '172.16.0.0/12'::inet OR {}::inet << '192.168.0.0/16'::inet OR {}::inet << '169.254.0.0/16'::inet)",
494 field_sql, field_sql, field_sql, field_sql
495 ))
496 }
497
498 WhereOperator::IsPublic(field) => {
499 let field_sql = field.to_sql();
500 Ok(format!(
502 "NOT ({}::inet << '10.0.0.0/8'::inet OR {}::inet << '172.16.0.0/12'::inet OR {}::inet << '192.168.0.0/16'::inet OR {}::inet << '169.254.0.0/16'::inet)",
503 field_sql, field_sql, field_sql, field_sql
504 ))
505 }
506
507 WhereOperator::IsLoopback(field) => {
508 let field_sql = field.to_sql();
509 Ok(format!(
510 "(family({}::inet) = 4 AND {}::inet << '127.0.0.0/8'::inet) OR (family({}::inet) = 6 AND {}::inet << '::1/128'::inet)",
511 field_sql, field_sql, field_sql, field_sql
512 ))
513 }
514
515 WhereOperator::InSubnet { field, subnet } => {
516 let field_sql = field.to_sql();
517 let param_num = *param_index + 1;
518 *param_index += 1;
519 params.insert(param_num, Value::String(subnet.clone()));
520 Ok(format!("{}::inet << ${}::inet", field_sql, param_num))
521 }
522
523 WhereOperator::ContainsSubnet { field, subnet } => {
524 let field_sql = field.to_sql();
525 let param_num = *param_index + 1;
526 *param_index += 1;
527 params.insert(param_num, Value::String(subnet.clone()));
528 Ok(format!("{}::inet >> ${}::inet", field_sql, param_num))
529 }
530
531 WhereOperator::ContainsIP { field, ip } => {
532 let field_sql = field.to_sql();
533 let param_num = *param_index + 1;
534 *param_index += 1;
535 params.insert(param_num, Value::String(ip.clone()));
536 Ok(format!("{}::inet >> ${}::inet", field_sql, param_num))
537 }
538
539 WhereOperator::IPRangeOverlap { field, range } => {
540 let field_sql = field.to_sql();
541 let param_num = *param_index + 1;
542 *param_index += 1;
543 params.insert(param_num, Value::String(range.clone()));
544 Ok(format!("{}::inet && ${}::inet", field_sql, param_num))
545 }
546
547 WhereOperator::StrictlyContains(field, value) => {
549 let field_sql = field.to_sql();
550 let param_num = *param_index + 1;
551 *param_index += 1;
552 params.insert(param_num, value.clone());
553 Ok(format!("{}::jsonb @> ${}::jsonb", field_sql, param_num))
554 }
555
556 WhereOperator::AncestorOf { field, path } => {
558 let field_sql = field.to_sql();
559 let param_num = *param_index + 1;
560 *param_index += 1;
561 params.insert(param_num, Value::String(path.clone()));
562 Ok(format!("{}::ltree @> ${}::ltree", field_sql, param_num))
563 }
564
565 WhereOperator::DescendantOf { field, path } => {
566 let field_sql = field.to_sql();
567 let param_num = *param_index + 1;
568 *param_index += 1;
569 params.insert(param_num, Value::String(path.clone()));
570 Ok(format!("{}::ltree <@ ${}::ltree", field_sql, param_num))
571 }
572
573 WhereOperator::MatchesLquery { field, pattern } => {
574 let field_sql = field.to_sql();
575 let param_num = *param_index + 1;
576 *param_index += 1;
577 params.insert(param_num, Value::String(pattern.clone()));
578 Ok(format!("{}::ltree ~ ${}::lquery", field_sql, param_num))
579 }
580
581 WhereOperator::MatchesLtxtquery { field, query } => {
582 let field_sql = field.to_sql();
583 let param_num = *param_index + 1;
584 *param_index += 1;
585 params.insert(param_num, Value::String(query.clone()));
586 Ok(format!("{}::ltree @ ${}::ltxtquery", field_sql, param_num))
587 }
588
589 WhereOperator::MatchesAnyLquery { field, patterns } => {
590 let field_sql = field.to_sql();
591 let placeholders: Vec<String> = patterns
592 .iter()
593 .map(|p| {
594 let param_num = *param_index + 1;
595 *param_index += 1;
596 params.insert(param_num, Value::String(p.clone()));
597 format!("${}::lquery", param_num)
598 })
599 .collect();
600 Ok(format!(
601 "{}::ltree ? ARRAY[{}]",
602 field_sql,
603 placeholders.join(", ")
604 ))
605 }
606
607 WhereOperator::DepthEq { field, depth } => {
609 let field_sql = field.to_sql();
610 Ok(format!("nlevel({}::ltree) = {}", field_sql, depth))
611 }
612
613 WhereOperator::DepthNeq { field, depth } => {
614 let field_sql = field.to_sql();
615 Ok(format!("nlevel({}::ltree) != {}", field_sql, depth))
616 }
617
618 WhereOperator::DepthGt { field, depth } => {
619 let field_sql = field.to_sql();
620 Ok(format!("nlevel({}::ltree) > {}", field_sql, depth))
621 }
622
623 WhereOperator::DepthGte { field, depth } => {
624 let field_sql = field.to_sql();
625 Ok(format!("nlevel({}::ltree) >= {}", field_sql, depth))
626 }
627
628 WhereOperator::DepthLt { field, depth } => {
629 let field_sql = field.to_sql();
630 Ok(format!("nlevel({}::ltree) < {}", field_sql, depth))
631 }
632
633 WhereOperator::DepthLte { field, depth } => {
634 let field_sql = field.to_sql();
635 Ok(format!("nlevel({}::ltree) <= {}", field_sql, depth))
636 }
637
638 WhereOperator::Lca { field, paths } => {
640 let field_sql = field.to_sql();
641 let placeholders: Vec<String> = paths
642 .iter()
643 .map(|p| {
644 let param_num = *param_index + 1;
645 *param_index += 1;
646 params.insert(param_num, Value::String(p.clone()));
647 format!("${}::ltree", param_num)
648 })
649 .collect();
650 Ok(format!(
651 "{}::ltree = lca(ARRAY[{}])",
652 field_sql,
653 placeholders.join(", ")
654 ))
655 }
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use super::*;
662
663 #[test]
664 fn test_eq_operator_jsonb_string() {
665 let mut param_index = 0;
666 let mut params = HashMap::new();
667 let op = WhereOperator::Eq(
668 Field::JsonbField("name".to_string()),
669 Value::String("John".to_string()),
670 );
671 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
672 assert_eq!(sql, "(data->'name')::text = $1");
674 assert_eq!(param_index, 1);
675 }
676
677 #[test]
678 fn test_eq_operator_direct_column() {
679 let mut param_index = 0;
680 let mut params = HashMap::new();
681 let op = WhereOperator::Eq(
682 Field::DirectColumn("status".to_string()),
683 Value::String("active".to_string()),
684 );
685 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
686 assert_eq!(sql, "status = $1");
688 assert_eq!(param_index, 1);
689 }
690
691 #[test]
692 fn test_len_eq_operator() {
693 let mut param_index = 0;
694 let mut params = HashMap::new();
695 let op = WhereOperator::LenEq(Field::JsonbField("tags".to_string()), 5);
696 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
697 assert_eq!(sql, "array_length((data->'tags'), 1) = 5");
698 assert_eq!(param_index, 0); }
700
701 #[test]
702 fn test_is_ipv4_operator() {
703 let mut param_index = 0;
704 let mut params = HashMap::new();
705 let op = WhereOperator::IsIPv4(Field::JsonbField("ip".to_string()));
706 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
707 assert_eq!(sql, "family((data->'ip')::inet) = 4");
708 }
709
710 #[test]
711 fn test_l2_distance_operator() {
712 let mut param_index = 0;
713 let mut params = HashMap::new();
714 let op = WhereOperator::L2Distance {
715 field: Field::JsonbField("embedding".to_string()),
716 vector: vec![0.1, 0.2, 0.3],
717 threshold: 0.5,
718 };
719 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
720 assert_eq!(
721 sql,
722 "l2_distance((data->'embedding')::vector, $1::vector) < 0.5"
723 );
724 assert_eq!(param_index, 1);
725 }
726
727 #[test]
728 fn test_in_operator() {
729 let mut param_index = 0;
730 let mut params = HashMap::new();
731 let op = WhereOperator::In(
732 Field::JsonbField("status".to_string()),
733 vec![
734 Value::String("active".to_string()),
735 Value::String("pending".to_string()),
736 ],
737 );
738 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
739 assert_eq!(sql, "(data->'status') IN ($1, $2)");
740 assert_eq!(param_index, 2);
741 }
742
743 #[test]
746 fn test_ltree_ancestor_of() {
747 let mut param_index = 0;
748 let mut params = HashMap::new();
749 let op = WhereOperator::AncestorOf {
750 field: Field::DirectColumn("path".to_string()),
751 path: "Top.Sciences.Astronomy".to_string(),
752 };
753 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
754 assert_eq!(sql, "path::ltree @> $1::ltree");
755 assert_eq!(param_index, 1);
756 }
757
758 #[test]
759 fn test_ltree_descendant_of() {
760 let mut param_index = 0;
761 let mut params = HashMap::new();
762 let op = WhereOperator::DescendantOf {
763 field: Field::DirectColumn("path".to_string()),
764 path: "Top.Sciences".to_string(),
765 };
766 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
767 assert_eq!(sql, "path::ltree <@ $1::ltree");
768 assert_eq!(param_index, 1);
769 }
770
771 #[test]
772 fn test_ltree_matches_lquery() {
773 let mut param_index = 0;
774 let mut params = HashMap::new();
775 let op = WhereOperator::MatchesLquery {
776 field: Field::DirectColumn("path".to_string()),
777 pattern: "Top.*.Ast*".to_string(),
778 };
779 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
780 assert_eq!(sql, "path::ltree ~ $1::lquery");
781 assert_eq!(param_index, 1);
782 }
783
784 #[test]
785 fn test_ltree_matches_ltxtquery() {
786 let mut param_index = 0;
787 let mut params = HashMap::new();
788 let op = WhereOperator::MatchesLtxtquery {
789 field: Field::DirectColumn("path".to_string()),
790 query: "Science & !Deprecated".to_string(),
791 };
792 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
793 assert_eq!(sql, "path::ltree @ $1::ltxtquery");
794 assert_eq!(param_index, 1);
795 }
796
797 #[test]
798 fn test_ltree_matches_any_lquery() {
799 let mut param_index = 0;
800 let mut params = HashMap::new();
801 let op = WhereOperator::MatchesAnyLquery {
802 field: Field::DirectColumn("path".to_string()),
803 patterns: vec!["Top.*".to_string(), "Other.*".to_string()],
804 };
805 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
806 assert_eq!(sql, "path::ltree ? ARRAY[$1::lquery, $2::lquery]");
807 assert_eq!(param_index, 2);
808 }
809
810 #[test]
811 fn test_ltree_depth_eq() {
812 let mut param_index = 0;
813 let mut params = HashMap::new();
814 let op = WhereOperator::DepthEq {
815 field: Field::DirectColumn("path".to_string()),
816 depth: 3,
817 };
818 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
819 assert_eq!(sql, "nlevel(path::ltree) = 3");
820 assert_eq!(param_index, 0); }
822
823 #[test]
824 fn test_ltree_depth_gt() {
825 let mut param_index = 0;
826 let mut params = HashMap::new();
827 let op = WhereOperator::DepthGt {
828 field: Field::DirectColumn("path".to_string()),
829 depth: 2,
830 };
831 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
832 assert_eq!(sql, "nlevel(path::ltree) > 2");
833 assert_eq!(param_index, 0);
834 }
835
836 #[test]
837 fn test_ltree_depth_lte() {
838 let mut param_index = 0;
839 let mut params = HashMap::new();
840 let op = WhereOperator::DepthLte {
841 field: Field::DirectColumn("path".to_string()),
842 depth: 5,
843 };
844 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
845 assert_eq!(sql, "nlevel(path::ltree) <= 5");
846 assert_eq!(param_index, 0);
847 }
848
849 #[test]
850 fn test_ltree_lca() {
851 let mut param_index = 0;
852 let mut params = HashMap::new();
853 let op = WhereOperator::Lca {
854 field: Field::DirectColumn("path".to_string()),
855 paths: vec![
856 "Org.Engineering.Backend".to_string(),
857 "Org.Engineering.Frontend".to_string(),
858 ],
859 };
860 let sql = generate_where_operator_sql(&op, &mut param_index, &mut params).unwrap();
861 assert_eq!(sql, "path::ltree = lca(ARRAY[$1::ltree, $2::ltree])");
862 assert_eq!(param_index, 2);
863 }
864}