1use fraiseql_error::{FraiseQLError, Result};
4
5use super::counter::ParamCounter;
6use crate::{
7 dialect::SqlDialect,
8 types::sql_hints::ScalarFieldType,
9 where_clause::{FieldTypeMap, WhereClause, WhereOperator, field_types::operand_type},
10};
11
12fn uuid_case_variants(value: &serde_json::Value) -> Option<(String, String)> {
25 let raw = value.as_str()?;
26 let canonical = uuid::Uuid::parse_str(raw).ok()?.hyphenated().to_string();
27 if raw == canonical || !raw.eq_ignore_ascii_case(&canonical) {
28 return None;
29 }
30 Some((raw.to_string(), canonical))
31}
32
33enum VectorOperand<'a> {
40 Native(&'a str),
52 Json(&'a str),
55}
56
57impl VectorOperand<'_> {
58 fn render(&self, cast: &str) -> String {
61 match *self {
62 Self::Native(column) => column.to_string(),
63 Self::Json(expr) => format!("({expr})::{cast}"),
66 }
67 }
68}
69
70pub(crate) fn escape_like_literal(s: &str) -> String {
75 s.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_")
76}
77
78const MAX_REGEX_PATTERN_LEN: usize = 1_000;
83
84fn validate_regex_pattern(pattern: &str) -> Result<()> {
92 if pattern.len() > MAX_REGEX_PATTERN_LEN {
93 return Err(FraiseQLError::Validation {
94 message: format!(
95 "Regex pattern exceeds maximum length of {MAX_REGEX_PATTERN_LEN} bytes"
96 ),
97 path: None,
98 });
99 }
100
101 let bytes = pattern.as_bytes();
105 let mut depth: i32 = 0;
106 let mut group_has_quantifier = Vec::new(); for (i, &b) in bytes.iter().enumerate() {
109 if i > 0 && bytes[i - 1] == b'\\' {
111 continue;
112 }
113 match b {
114 b'(' => {
115 depth += 1;
116 group_has_quantifier.push(false);
117 },
118 b')' => {
119 let had_quantifier = group_has_quantifier.pop().unwrap_or(false);
120 depth -= 1;
121 if had_quantifier {
123 let next = bytes.get(i + 1).copied();
124 if matches!(next, Some(b'+' | b'*' | b'?' | b'{')) {
125 return Err(FraiseQLError::Validation {
126 message: "Regex pattern contains nested quantifiers (potential \
127 ReDoS). Simplify the pattern to avoid `(…+)+`, \
128 `(…*)*`, or similar constructs."
129 .to_string(),
130 path: None,
131 });
132 }
133 }
134 },
135 b'+' | b'*' | b'?' => {
136 if let Some(flag) = group_has_quantifier.last_mut() {
137 *flag = true;
138 }
139 },
140 b'{' if depth > 0 => {
141 if let Some(flag) = group_has_quantifier.last_mut() {
142 *flag = true;
143 }
144 },
145 _ => {},
146 }
147 }
148
149 Ok(())
150}
151
152pub struct GenericWhereGenerator<D: SqlDialect> {
183 dialect: D,
184 counter: ParamCounter,
185}
186
187impl<D: SqlDialect> GenericWhereGenerator<D> {
188 pub const fn new(dialect: D) -> Self {
190 Self {
191 dialect,
192 counter: ParamCounter::new(),
193 }
194 }
195
196 pub fn generate(&self, clause: &WhereClause) -> Result<(String, Vec<serde_json::Value>)> {
203 self.generate_with_param_offset(clause, 0)
204 }
205
206 pub fn generate_with_param_offset(
216 &self,
217 clause: &WhereClause,
218 offset: usize,
219 ) -> Result<(String, Vec<serde_json::Value>)> {
220 self.counter.reset_to(offset);
221 let mut params = Vec::new();
222 let sql = self.visit(clause, &mut params)?;
223 Ok((sql, params))
224 }
225
226 fn visit(&self, clause: &WhereClause, params: &mut Vec<serde_json::Value>) -> Result<String> {
229 self.visit_impl(clause, params, None, None, &[])
230 }
231
232 fn visit_impl(
233 &self,
234 clause: &WhereClause,
235 params: &mut Vec<serde_json::Value>,
236 hierarchy_ctx: Option<&super::HierarchyContext>,
237 types: Option<&FieldTypeMap>,
238 guards: &[(&[String], &WhereClause)],
239 ) -> Result<String> {
240 match clause {
241 WhereClause::And(clauses) => {
242 if clauses.is_empty() {
243 return Ok(self.dialect.always_true().to_string());
244 }
245 let parts: Result<Vec<_>> = clauses
246 .iter()
247 .map(|c| self.visit_impl(c, params, hierarchy_ctx, types, guards))
248 .collect();
249 Ok(format!("({})", parts?.join(" AND ")))
250 },
251 WhereClause::Or(clauses) => {
252 if clauses.is_empty() {
253 return Ok(self.dialect.always_false().to_string());
254 }
255 let parts: Result<Vec<_>> = clauses
256 .iter()
257 .map(|c| self.visit_impl(c, params, hierarchy_ctx, types, guards))
258 .collect();
259 Ok(format!("({})", parts?.join(" OR ")))
260 },
261 WhereClause::Not(inner) => Ok(format!(
262 "NOT ({})",
263 self.visit_impl(inner, params, hierarchy_ctx, types, guards)?
264 )),
265 WhereClause::Typed {
266 types: subtree_types,
267 inner,
268 } => self.visit_impl(inner, params, hierarchy_ctx, Some(subtree_types), guards),
269 WhereClause::InHierarchy { context, inner } => {
271 self.visit_impl(inner, params, Some(context), types, guards)
272 },
273 WhereClause::Guarded {
275 under,
276 guard,
277 inner,
278 } => {
279 let mut scoped = guards.to_vec();
280 scoped.push((under.as_slice(), guard.as_ref()));
281 self.visit_impl(inner, params, hierarchy_ctx, types, &scoped)
282 },
283 WhereClause::Field {
284 path,
285 operator,
286 value,
287 } => self.visit_field(path, operator, value, params, hierarchy_ctx, types, guards),
288 WhereClause::NativeField {
289 column,
290 pg_cast,
291 operator,
292 value,
293 } => self.visit_native_field(column, pg_cast, operator, value, params),
294 WhereClause::KeyIn {
297 path,
298 key_type,
299 view,
300 target_key,
301 predicate,
302 } => {
303 let key = self
304 .dialect
305 .cast_expr_as(&self.dialect.json_extract_scalar("data", path), *key_type)
306 .into_owned();
307 let target = self
308 .dialect
309 .cast_expr_as(&self.dialect.json_extract_scalar("data", target_key), *key_type)
310 .into_owned();
311 let view = view
312 .split('.')
313 .map(|part| self.dialect.quote_identifier(part))
314 .collect::<Vec<_>>()
315 .join(".");
316 let predicate = self.visit_impl(predicate, params, None, None, &[])?;
317 Ok(format!("({key} IN (SELECT {target} FROM {view} WHERE {predicate}))"))
318 },
319 }
320 }
321
322 fn visit_native_field(
328 &self,
329 column: &str,
330 pg_cast: &str,
331 operator: &WhereOperator,
332 value: &serde_json::Value,
333 params: &mut Vec<serde_json::Value>,
334 ) -> Result<String> {
335 let col_expr = self.dialect.quote_identifier(column);
336 let p = self.push_param(params, value.clone());
337 let rhs = if pg_cast.is_empty() {
338 p
339 } else {
340 self.dialect.cast_native_param(&p, pg_cast)
341 };
342 match operator {
343 WhereOperator::Eq => Ok(format!("{col_expr} = {rhs}")),
344 WhereOperator::Neq => {
345 let neq = self.dialect.neq_operator();
346 Ok(format!("{col_expr} {neq} {rhs}"))
347 },
348 _ => Err(FraiseQLError::validation(format!(
349 "Operator {operator:?} is not supported for native column conditions"
350 ))),
351 }
352 }
353
354 fn vector_native_column(
377 &self,
378 path: &[String],
379 types: Option<&FieldTypeMap>,
380 ) -> Option<String> {
381 if path.len() != 1 {
382 return None;
383 }
384 if types?.get(path)? != ScalarFieldType::Vector {
385 return None;
386 }
387 let column = crate::utils::vector_storage_column(path.first()?).ok()?;
388 Some(self.dialect.quote_identifier(&column))
389 }
390
391 fn guarded_expr(
396 &self,
397 expr: String,
398 path: &[String],
399 params: &mut Vec<serde_json::Value>,
400 guards: &[(&[String], &WhereClause)],
401 ) -> Result<String> {
402 let mut conditions = Vec::new();
403 for (under, guard) in guards {
404 if path.len() > under.len() && path.starts_with(under) {
405 conditions.push(self.visit_impl(guard, params, None, None, &[])?);
406 }
407 }
408 if conditions.is_empty() {
409 return Ok(expr);
410 }
411 Ok(format!("(CASE WHEN {} THEN {expr} END)", conditions.join(" AND ")))
412 }
413
414 fn resolve_field_expr(&self, path: &[String]) -> String {
415 self.dialect.json_extract_scalar("data", path)
416 }
417
418 fn push_param(&self, params: &mut Vec<serde_json::Value>, v: serde_json::Value) -> String {
421 params.push(v);
422 self.dialect.placeholder(self.counter.next())
423 }
424
425 fn vector_threshold_sql(
441 &self,
442 field: &VectorOperand<'_>,
443 op: &str,
444 value: &serde_json::Value,
445 params: &mut Vec<serde_json::Value>,
446 negate_threshold: bool,
447 ) -> Result<String> {
448 let shape_err = || {
449 FraiseQLError::validation(
450 "vector operators take {vector: [Float, ...], threshold: Float} — e.g. \
451 {cosine_distance: {vector: [0.1, 0.2], threshold: 0.5}}",
452 )
453 };
454 let obj = value.as_object().ok_or_else(shape_err)?;
455 let operand = obj.get("vector").ok_or_else(shape_err)?;
456 let threshold =
457 obj.get("threshold").and_then(serde_json::Value::as_f64).ok_or_else(shape_err)?;
458 if !threshold.is_finite() {
459 return Err(FraiseQLError::validation("vector threshold must be a finite number"));
460 }
461 if let Some(sparse) = operand.as_str() {
467 return self.sparse_threshold_sql(
468 field,
469 op,
470 sparse,
471 threshold,
472 params,
473 negate_threshold,
474 );
475 }
476 let vector = operand.as_array().ok_or_else(shape_err)?;
477 if vector.is_empty() {
478 return Err(FraiseQLError::validation("vector operand must not be empty"));
479 }
480 let mut literal = String::with_capacity(vector.len() * 8 + 2);
481 literal.push('[');
482 for (i, component) in vector.iter().enumerate() {
483 let n = component.as_f64().filter(|n| n.is_finite()).ok_or_else(|| {
484 FraiseQLError::validation("vector operand components must all be finite numbers")
485 })?;
486 if i > 0 {
487 literal.push(',');
488 }
489 std::fmt::Write::write_fmt(&mut literal, format_args!("{n}"))
491 .expect("write to String is infallible");
492 }
493 literal.push(']');
494
495 let vector_param = self.push_param(params, serde_json::Value::String(literal));
496 let bound = if negate_threshold {
497 -threshold
498 } else {
499 threshold
500 };
501 let threshold_param = self.push_param(params, serde_json::json!(bound));
502 let lhs = field.render("vector");
510 Ok(format!(
511 "({lhs} {op} {vector_param}::text::vector) <= {threshold_param}::text::float8"
512 ))
513 }
514
515 #[allow(clippy::too_many_arguments)] fn sparse_threshold_sql(
534 &self,
535 field: &VectorOperand<'_>,
536 op: &str,
537 sparse: &str,
538 threshold: f64,
539 params: &mut Vec<serde_json::Value>,
540 negate_threshold: bool,
541 ) -> Result<String> {
542 let valid = sparse.starts_with('{')
543 && sparse.contains("}/")
544 && sparse.chars().all(|c| {
545 c.is_ascii_digit()
546 || matches!(c, '{' | '}' | '/' | ':' | ',' | '.' | '-' | '+' | 'e' | 'E')
547 });
548 if !valid {
549 return Err(FraiseQLError::validation(
550 "a sparse vector operand is pgvector's `{index:value,...}/dimensions` form — \
551 e.g. {cosine_distance: {vector: \"{1:0.5,7:0.25}/1000\", threshold: 0.25}}",
552 ));
553 }
554 let vector_param = self.push_param(params, serde_json::Value::String(sparse.to_string()));
555 let bound = if negate_threshold {
556 -threshold
557 } else {
558 threshold
559 };
560 let threshold_param = self.push_param(params, serde_json::json!(bound));
561 let lhs = field.render("sparsevec");
562 Ok(format!(
563 "({lhs} {op} {vector_param}::text::sparsevec) <= {threshold_param}::text::float8"
564 ))
565 }
566
567 fn bit_threshold_sql(
584 &self,
585 field: &VectorOperand<'_>,
586 op: &str,
587 value: &serde_json::Value,
588 params: &mut Vec<serde_json::Value>,
589 ) -> Result<String> {
590 let shape_err = || {
591 FraiseQLError::validation(
592 "binary vector operators take {vector: \"1011…\", threshold: Float} — e.g. \
593 {hamming_distance: {vector: \"11110000\", threshold: 2}}",
594 )
595 };
596 let obj = value.as_object().ok_or_else(shape_err)?;
597 let bits = obj.get("vector").and_then(serde_json::Value::as_str).ok_or_else(shape_err)?;
598 let threshold =
599 obj.get("threshold").and_then(serde_json::Value::as_f64).ok_or_else(shape_err)?;
600 if !threshold.is_finite() {
601 return Err(FraiseQLError::validation("vector threshold must be a finite number"));
602 }
603 if bits.is_empty() || !bits.chars().all(|c| matches!(c, '0' | '1')) {
604 return Err(FraiseQLError::validation(
605 "a binary vector operand must be a non-empty run of '0' and '1' characters",
606 ));
607 }
608
609 let vector_param = self.push_param(params, serde_json::Value::String(bits.to_string()));
610 let threshold_param = self.push_param(params, serde_json::json!(threshold));
611 let lhs = field.render("varbit");
616 Ok(format!(
617 "({lhs} {op} {vector_param}::text::varbit) <= {threshold_param}::text::float8"
618 ))
619 }
620
621 fn visit_field(
624 &self,
625 path: &[String],
626 operator: &WhereOperator,
627 value: &serde_json::Value,
628 params: &mut Vec<serde_json::Value>,
629 hierarchy_ctx: Option<&super::HierarchyContext>,
630 types: Option<&FieldTypeMap>,
631 guards: &[(&[String], &WhereClause)],
632 ) -> Result<String> {
633 let field_expr = self.guarded_expr(self.resolve_field_expr(path), path, params, guards)?;
634
635 match operator {
636 WhereOperator::Eq
641 | WhereOperator::Neq
642 | WhereOperator::Gt
643 | WhereOperator::Gte
644 | WhereOperator::Lt
645 | WhereOperator::Lte => {
646 let op = match operator {
647 WhereOperator::Eq => "=",
648 WhereOperator::Neq => self.dialect.neq_operator(),
649 WhereOperator::Gt => ">",
650 WhereOperator::Gte => ">=",
651 WhereOperator::Lt => "<",
652 _ => "<=",
653 };
654 let ty = operand_type(types, path, value);
655 let lhs = self.dialect.cast_expr_as(&field_expr, ty);
656
657 if matches!(ty, ScalarFieldType::Uuid)
668 && matches!(operator, WhereOperator::Eq | WhereOperator::Neq)
669 {
670 if let Some((as_given, lowered)) = uuid_case_variants(value) {
671 let a = self.push_param(params, serde_json::Value::String(as_given));
672 let b = self.push_param(params, serde_json::Value::String(lowered));
673 let quantifier = if matches!(operator, WhereOperator::Eq) {
674 "ANY"
675 } else {
676 "ALL"
677 };
678 return Ok(format!("{lhs} {op} {quantifier}(ARRAY[{a}, {b}])"));
679 }
680 }
681
682 let p = self.push_param(params, value.clone());
683 let rhs = self.dialect.cast_param_as(&p, ty);
684 Ok(format!("{lhs} {op} {rhs}"))
685 },
686
687 WhereOperator::In | WhereOperator::Nin => {
689 let arr = value.as_array().ok_or_else(|| {
690 FraiseQLError::validation("IN operator requires an array value".to_string())
691 })?;
692 if arr.is_empty() {
693 return Ok(if matches!(operator, WhereOperator::In) {
694 self.dialect.always_false().to_string()
695 } else {
696 self.dialect.always_true().to_string()
697 });
698 }
699 let ty = operand_type(types, path, value);
700 let lhs = self.dialect.cast_expr_as(&field_expr, ty);
701 let placeholders: Vec<_> = arr
702 .iter()
703 .flat_map(|v| {
704 if matches!(ty, ScalarFieldType::Uuid) {
707 if let Some((as_given, lowered)) = uuid_case_variants(v) {
708 let a =
709 self.push_param(params, serde_json::Value::String(as_given));
710 let b = self.push_param(params, serde_json::Value::String(lowered));
711 return vec![a, b];
712 }
713 }
714 let p = self.push_param(params, v.clone());
715 vec![self.dialect.cast_param_as(&p, ty).into_owned()]
716 })
717 .collect();
718 let in_list = placeholders.join(", ");
719 let sql = format!("{lhs} IN ({in_list})");
720 Ok(if matches!(operator, WhereOperator::Nin) {
721 format!("NOT ({sql})")
722 } else {
723 sql
724 })
725 },
726
727 WhereOperator::IsNull | WhereOperator::IsNotNull => {
729 let negated = matches!(operator, WhereOperator::IsNotNull);
730 let asserted = match value {
734 serde_json::Value::Bool(b) => *b,
735 serde_json::Value::Null => true,
736 other => {
737 return Err(FraiseQLError::validation(format!(
738 "{operator:?} requires a boolean operand, got {other}"
739 )));
740 },
741 };
742 let null_op = if asserted != negated {
743 "IS NULL"
744 } else {
745 "IS NOT NULL"
746 };
747 Ok(format!("{field_expr} {null_op}"))
748 },
749
750 WhereOperator::Contains => {
752 let val_str = self.require_str(value, "Contains")?;
753 let escaped = escape_like_literal(val_str);
754 let p = self.push_param(params, serde_json::Value::String(escaped));
755 let pattern = self.dialect.concat_sql(&["'%'", &p, "'%'"]);
756 Ok(self.dialect.like_sql(&field_expr, &pattern))
757 },
758 WhereOperator::Icontains => {
759 let val_str = self.require_str(value, "Icontains")?;
760 let escaped = escape_like_literal(val_str);
761 let p = self.push_param(params, serde_json::Value::String(escaped));
762 let pattern = self.dialect.concat_sql(&["'%'", &p, "'%'"]);
763 Ok(self.dialect.ilike_sql(&field_expr, &pattern))
764 },
765 WhereOperator::Startswith => {
766 let val_str = self.require_str(value, "Startswith")?;
767 let escaped = escape_like_literal(val_str);
768 let p = self.push_param(params, serde_json::Value::String(escaped));
769 let pattern = self.dialect.concat_sql(&[&p, "'%'"]);
770 Ok(self.dialect.like_sql(&field_expr, &pattern))
771 },
772 WhereOperator::Istartswith => {
773 let val_str = self.require_str(value, "Istartswith")?;
774 let escaped = escape_like_literal(val_str);
775 let p = self.push_param(params, serde_json::Value::String(escaped));
776 let pattern = self.dialect.concat_sql(&[&p, "'%'"]);
777 Ok(self.dialect.ilike_sql(&field_expr, &pattern))
778 },
779 WhereOperator::Endswith => {
780 let val_str = self.require_str(value, "Endswith")?;
781 let escaped = escape_like_literal(val_str);
782 let p = self.push_param(params, serde_json::Value::String(escaped));
783 let pattern = self.dialect.concat_sql(&["'%'", &p]);
784 Ok(self.dialect.like_sql(&field_expr, &pattern))
785 },
786 WhereOperator::Iendswith => {
787 let val_str = self.require_str(value, "Iendswith")?;
788 let escaped = escape_like_literal(val_str);
789 let p = self.push_param(params, serde_json::Value::String(escaped));
790 let pattern = self.dialect.concat_sql(&["'%'", &p]);
791 Ok(self.dialect.ilike_sql(&field_expr, &pattern))
792 },
793 WhereOperator::Like => {
794 let p = self.push_param(params, value.clone());
795 Ok(self.dialect.like_sql(&field_expr, &p))
796 },
797 WhereOperator::Ilike => {
798 let p = self.push_param(params, value.clone());
799 Ok(self.dialect.ilike_sql(&field_expr, &p))
800 },
801 WhereOperator::Nlike => {
802 let p = self.push_param(params, value.clone());
803 Ok(format!("NOT ({})", self.dialect.like_sql(&field_expr, &p)))
804 },
805 WhereOperator::Nilike => {
806 let p = self.push_param(params, value.clone());
807 Ok(format!("NOT ({})", self.dialect.ilike_sql(&field_expr, &p)))
808 },
809
810 WhereOperator::Regex => {
812 if let Some(s) = value.as_str() {
813 validate_regex_pattern(s)?;
814 }
815 let p = self.push_param(params, value.clone());
816 self.dialect
817 .regex_sql(&field_expr, &p, false, false)
818 .map_err(|e| FraiseQLError::validation(e.to_string()))
819 },
820 WhereOperator::Iregex => {
821 if let Some(s) = value.as_str() {
822 validate_regex_pattern(s)?;
823 }
824 let p = self.push_param(params, value.clone());
825 self.dialect
826 .regex_sql(&field_expr, &p, true, false)
827 .map_err(|e| FraiseQLError::validation(e.to_string()))
828 },
829 WhereOperator::Nregex => {
830 if let Some(s) = value.as_str() {
831 validate_regex_pattern(s)?;
832 }
833 let p = self.push_param(params, value.clone());
834 self.dialect
835 .regex_sql(&field_expr, &p, false, true)
836 .map_err(|e| FraiseQLError::validation(e.to_string()))
837 },
838 WhereOperator::Niregex => {
839 if let Some(s) = value.as_str() {
840 validate_regex_pattern(s)?;
841 }
842 let p = self.push_param(params, value.clone());
843 self.dialect
844 .regex_sql(&field_expr, &p, true, true)
845 .map_err(|e| FraiseQLError::validation(e.to_string()))
846 },
847
848 WhereOperator::LenEq
850 | WhereOperator::LenNeq
851 | WhereOperator::LenGt
852 | WhereOperator::LenGte
853 | WhereOperator::LenLt
854 | WhereOperator::LenLte => {
855 let op = match operator {
856 WhereOperator::LenEq => "=",
857 WhereOperator::LenNeq => self.dialect.neq_operator(),
858 WhereOperator::LenGt => ">",
859 WhereOperator::LenGte => ">=",
860 WhereOperator::LenLt => "<",
861 _ => "<=",
862 };
863 let len_expr = self.dialect.json_array_length(&field_expr);
864 let p = self.push_param(params, value.clone());
865 Ok(format!("{len_expr} {op} {p}"))
866 },
867
868 WhereOperator::ArrayContains | WhereOperator::StrictlyContains => {
870 let p = self.push_param(params, value.clone());
873 self.dialect
874 .array_contains_sql(&field_expr, &p)
875 .map_err(|e| FraiseQLError::validation(e.to_string()))
876 },
877 WhereOperator::ArrayContainedBy => {
878 let p = self.push_param(params, value.clone());
879 self.dialect
880 .array_contained_by_sql(&field_expr, &p)
881 .map_err(|e| FraiseQLError::validation(e.to_string()))
882 },
883 WhereOperator::ArrayOverlaps => {
884 let p = self.push_param(params, value.clone());
885 self.dialect
886 .array_overlaps_sql(&field_expr, &p)
887 .map_err(|e| FraiseQLError::validation(e.to_string()))
888 },
889
890 WhereOperator::Matches => {
892 let p = self.push_param(params, value.clone());
893 self.dialect
894 .fts_matches_sql(&field_expr, &p)
895 .map_err(|e| FraiseQLError::validation(e.to_string()))
896 },
897 WhereOperator::PlainQuery => {
898 let p = self.push_param(params, value.clone());
899 self.dialect
900 .fts_plain_query_sql(&field_expr, &p)
901 .map_err(|e| FraiseQLError::validation(e.to_string()))
902 },
903 WhereOperator::PhraseQuery => {
904 let p = self.push_param(params, value.clone());
905 self.dialect
906 .fts_phrase_query_sql(&field_expr, &p)
907 .map_err(|e| FraiseQLError::validation(e.to_string()))
908 },
909 WhereOperator::WebsearchQuery => {
910 let p = self.push_param(params, value.clone());
911 self.dialect
912 .fts_websearch_query_sql(&field_expr, &p)
913 .map_err(|e| FraiseQLError::validation(e.to_string()))
914 },
915
916 WhereOperator::CosineDistance
928 | WhereOperator::L2Distance
929 | WhereOperator::L1Distance
930 | WhereOperator::InnerProduct => {
931 let native = self.vector_native_column(path, types);
932 let field = native
933 .as_deref()
934 .map_or(VectorOperand::Json(field_expr.as_str()), VectorOperand::Native);
935 let (op, negate) = match operator {
936 WhereOperator::CosineDistance => ("<=>", false),
937 WhereOperator::L2Distance => ("<->", false),
938 WhereOperator::L1Distance => ("<+>", false),
939 _ => ("<#>", true),
941 };
942 self.vector_threshold_sql(&field, op, value, params, negate)
943 },
944 WhereOperator::HammingDistance | WhereOperator::JaccardDistance => {
947 let native = self.vector_native_column(path, types);
948 let field = native
949 .as_deref()
950 .map_or(VectorOperand::Json(field_expr.as_str()), VectorOperand::Native);
951 let op = if matches!(operator, WhereOperator::HammingDistance) {
952 "<~>"
953 } else {
954 "<%>"
955 };
956 self.bit_threshold_sql(&field, op, value, params)
957 },
958
959 WhereOperator::IsIPv4 => {
961 let negate = value.as_bool().is_some_and(|v| !v);
964 let check_name = if negate { "IsNotIPv4" } else { "IsIPv4" };
965 self.dialect
966 .inet_check_sql(&field_expr, check_name)
967 .map_err(|e| FraiseQLError::validation(e.to_string()))
968 },
969 WhereOperator::IsIPv6 => {
970 let negate = value.as_bool().is_some_and(|v| !v);
971 let check_name = if negate { "IsNotIPv6" } else { "IsIPv6" };
972 self.dialect
973 .inet_check_sql(&field_expr, check_name)
974 .map_err(|e| FraiseQLError::validation(e.to_string()))
975 },
976 WhereOperator::IsPrivate => {
977 let negate = value.as_bool().is_some_and(|v| !v);
978 let check_name = if negate { "IsPublic" } else { "IsPrivate" };
979 self.dialect
980 .inet_check_sql(&field_expr, check_name)
981 .map_err(|e| FraiseQLError::validation(e.to_string()))
982 },
983 WhereOperator::IsLoopback => {
984 let negate = value.as_bool().is_some_and(|v| !v);
985 let check_name = if negate {
986 "IsNotLoopback"
987 } else {
988 "IsLoopback"
989 };
990 self.dialect
991 .inet_check_sql(&field_expr, check_name)
992 .map_err(|e| FraiseQLError::validation(e.to_string()))
993 },
994 WhereOperator::IsMulticast => {
995 let negate = value.as_bool().is_some_and(|v| !v);
996 let check_name = if negate {
997 "IsNotMulticast"
998 } else {
999 "IsMulticast"
1000 };
1001 self.dialect
1002 .inet_check_sql(&field_expr, check_name)
1003 .map_err(|e| FraiseQLError::validation(e.to_string()))
1004 },
1005 WhereOperator::IsLinkLocal => {
1006 let negate = value.as_bool().is_some_and(|v| !v);
1007 let check_name = if negate {
1008 "IsNotLinkLocal"
1009 } else {
1010 "IsLinkLocal"
1011 };
1012 self.dialect
1013 .inet_check_sql(&field_expr, check_name)
1014 .map_err(|e| FraiseQLError::validation(e.to_string()))
1015 },
1016 WhereOperator::IsDocumentation => {
1017 let negate = value.as_bool().is_some_and(|v| !v);
1018 let check_name = if negate {
1019 "IsNotDocumentation"
1020 } else {
1021 "IsDocumentation"
1022 };
1023 self.dialect
1024 .inet_check_sql(&field_expr, check_name)
1025 .map_err(|e| FraiseQLError::validation(e.to_string()))
1026 },
1027 WhereOperator::IsCarrierGrade => {
1028 let negate = value.as_bool().is_some_and(|v| !v);
1029 let check_name = if negate {
1030 "IsNotCarrierGrade"
1031 } else {
1032 "IsCarrierGrade"
1033 };
1034 self.dialect
1035 .inet_check_sql(&field_expr, check_name)
1036 .map_err(|e| FraiseQLError::validation(e.to_string()))
1037 },
1038 WhereOperator::InSubnet => {
1039 let p = self.push_param(params, value.clone());
1040 self.dialect
1041 .inet_binary_sql("<<", &field_expr, &p)
1042 .map_err(|e| FraiseQLError::validation(e.to_string()))
1043 },
1044 WhereOperator::ContainsSubnet | WhereOperator::ContainsIP => {
1045 let p = self.push_param(params, value.clone());
1046 self.dialect
1047 .inet_binary_sql(">>", &field_expr, &p)
1048 .map_err(|e| FraiseQLError::validation(e.to_string()))
1049 },
1050 WhereOperator::Overlaps => {
1051 let p = self.push_param(params, value.clone());
1052 self.dialect
1053 .inet_binary_sql("&&", &field_expr, &p)
1054 .map_err(|e| FraiseQLError::validation(e.to_string()))
1055 },
1056
1057 WhereOperator::AncestorOf => {
1059 let p = self.push_param(params, value.clone());
1060 self.dialect
1061 .ltree_binary_sql("@>", &field_expr, &p, "ltree")
1062 .map_err(|e| FraiseQLError::validation(e.to_string()))
1063 },
1064 WhereOperator::DescendantOf => {
1065 let p = self.push_param(params, value.clone());
1066 self.dialect
1067 .ltree_binary_sql("<@", &field_expr, &p, "ltree")
1068 .map_err(|e| FraiseQLError::validation(e.to_string()))
1069 },
1070 WhereOperator::MatchesLquery => {
1071 let p = self.push_param(params, value.clone());
1072 self.dialect
1073 .ltree_binary_sql("~", &field_expr, &p, "lquery")
1074 .map_err(|e| FraiseQLError::validation(e.to_string()))
1075 },
1076 WhereOperator::MatchesLtxtquery => {
1077 let p = self.push_param(params, value.clone());
1078 self.dialect
1079 .ltree_binary_sql("@", &field_expr, &p, "ltxtquery")
1080 .map_err(|e| FraiseQLError::validation(e.to_string()))
1081 },
1082 WhereOperator::MatchesAnyLquery => {
1083 let arr = value.as_array().ok_or_else(|| {
1084 FraiseQLError::validation(
1085 "matches_any_lquery operator requires an array value".to_string(),
1086 )
1087 })?;
1088 if arr.is_empty() {
1089 return Err(FraiseQLError::validation(
1090 "matches_any_lquery requires at least one lquery".to_string(),
1091 ));
1092 }
1093 let placeholders: Vec<_> = arr
1094 .iter()
1095 .map(|v| format!("{}::lquery", self.push_param(params, v.clone())))
1096 .collect();
1097 self.dialect
1098 .ltree_any_lquery_sql(&field_expr, &placeholders)
1099 .map_err(|e| FraiseQLError::validation(e.to_string()))
1100 },
1101 WhereOperator::DepthEq => {
1102 let p = self.push_param(params, value.clone());
1103 self.dialect
1104 .ltree_depth_sql("=", &field_expr, &p)
1105 .map_err(|e| FraiseQLError::validation(e.to_string()))
1106 },
1107 WhereOperator::DepthNeq => {
1108 let p = self.push_param(params, value.clone());
1109 self.dialect
1110 .ltree_depth_sql("!=", &field_expr, &p)
1111 .map_err(|e| FraiseQLError::validation(e.to_string()))
1112 },
1113 WhereOperator::DepthGt => {
1114 let p = self.push_param(params, value.clone());
1115 self.dialect
1116 .ltree_depth_sql(">", &field_expr, &p)
1117 .map_err(|e| FraiseQLError::validation(e.to_string()))
1118 },
1119 WhereOperator::DepthGte => {
1120 let p = self.push_param(params, value.clone());
1121 self.dialect
1122 .ltree_depth_sql(">=", &field_expr, &p)
1123 .map_err(|e| FraiseQLError::validation(e.to_string()))
1124 },
1125 WhereOperator::DepthLt => {
1126 let p = self.push_param(params, value.clone());
1127 self.dialect
1128 .ltree_depth_sql("<", &field_expr, &p)
1129 .map_err(|e| FraiseQLError::validation(e.to_string()))
1130 },
1131 WhereOperator::DepthLte => {
1132 let p = self.push_param(params, value.clone());
1133 self.dialect
1134 .ltree_depth_sql("<=", &field_expr, &p)
1135 .map_err(|e| FraiseQLError::validation(e.to_string()))
1136 },
1137 WhereOperator::Lca => {
1138 let arr = value.as_array().ok_or_else(|| {
1139 FraiseQLError::validation("lca operator requires an array value".to_string())
1140 })?;
1141 if arr.is_empty() {
1142 return Err(FraiseQLError::validation(
1143 "lca operator requires at least one path".to_string(),
1144 ));
1145 }
1146 let placeholders: Vec<_> = arr
1147 .iter()
1148 .map(|v| format!("{}::ltree", self.push_param(params, v.clone())))
1149 .collect();
1150 self.dialect
1151 .ltree_lca_sql(&field_expr, &placeholders)
1152 .map_err(|e| FraiseQLError::validation(e.to_string()))
1153 },
1154
1155 WhereOperator::DescendantOfId | WhereOperator::AncestorOfId => {
1157 let ctx = hierarchy_ctx.ok_or_else(|| {
1158 FraiseQLError::validation(
1159 "descendantOfId/ancestorOfId reached SQL generation without its \
1160 field's hierarchy: the clause was built outside the `where` parser, \
1161 which attaches it (WhereClause::InHierarchy)"
1162 .to_string(),
1163 )
1164 })?;
1165 let pg_op = if matches!(operator, WhereOperator::DescendantOfId) {
1166 "<@"
1167 } else {
1168 "@>"
1169 };
1170 let p = self.push_param(params, value.clone());
1173 let p = self.dialect.cast_native_param(&p, "uuid");
1174 self.dialect
1175 .ltree_id_subquery_sql(
1176 pg_op,
1177 &field_expr,
1178 &ctx.table,
1179 &ctx.path_column,
1180 ctx.fk_column.as_deref(),
1181 &p,
1182 )
1183 .map_err(|e| FraiseQLError::validation(e.to_string()))
1184 },
1185
1186 #[allow(unreachable_patterns)]
1191 _ => Err(FraiseQLError::Validation {
1193 message: format!(
1194 "Operator {operator:?} is not supported by the {} dialect",
1195 self.dialect.name()
1196 ),
1197 path: None,
1198 }),
1199 }
1200 }
1201
1202 fn require_str<'a>(&self, value: &'a serde_json::Value, op: &'static str) -> Result<&'a str> {
1203 value.as_str().ok_or_else(|| {
1204 FraiseQLError::validation(format!("{op} operator requires a string value"))
1205 })
1206 }
1207}
1208
1209impl<D: SqlDialect + Default> Default for GenericWhereGenerator<D> {
1212 fn default() -> Self {
1213 Self::new(D::default())
1214 }
1215}
1216
1217#[cfg(test)]
1218mod tests;