1use std::{collections::HashSet, sync::Arc};
4
5use fraiseql_error::{FraiseQLError, Result};
6
7use super::counter::ParamCounter;
8use crate::{
9 dialect::SqlDialect,
10 where_clause::{WhereClause, WhereOperator},
11};
12
13pub(crate) fn escape_like_literal(s: &str) -> String {
18 s.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_")
19}
20
21const MAX_REGEX_PATTERN_LEN: usize = 1_000;
26
27fn validate_regex_pattern(pattern: &str) -> Result<()> {
35 if pattern.len() > MAX_REGEX_PATTERN_LEN {
36 return Err(FraiseQLError::Validation {
37 message: format!(
38 "Regex pattern exceeds maximum length of {MAX_REGEX_PATTERN_LEN} bytes"
39 ),
40 path: None,
41 });
42 }
43
44 let bytes = pattern.as_bytes();
48 let mut depth: i32 = 0;
49 let mut group_has_quantifier = Vec::new(); for (i, &b) in bytes.iter().enumerate() {
52 if i > 0 && bytes[i - 1] == b'\\' {
54 continue;
55 }
56 match b {
57 b'(' => {
58 depth += 1;
59 group_has_quantifier.push(false);
60 },
61 b')' => {
62 let had_quantifier = group_has_quantifier.pop().unwrap_or(false);
63 depth -= 1;
64 if had_quantifier {
66 let next = bytes.get(i + 1).copied();
67 if matches!(next, Some(b'+' | b'*' | b'?' | b'{')) {
68 return Err(FraiseQLError::Validation {
69 message: "Regex pattern contains nested quantifiers (potential \
70 ReDoS). Simplify the pattern to avoid `(…+)+`, \
71 `(…*)*`, or similar constructs."
72 .to_string(),
73 path: None,
74 });
75 }
76 }
77 },
78 b'+' | b'*' | b'?' => {
79 if let Some(flag) = group_has_quantifier.last_mut() {
80 *flag = true;
81 }
82 },
83 b'{' if depth > 0 => {
84 if let Some(flag) = group_has_quantifier.last_mut() {
85 *flag = true;
86 }
87 },
88 _ => {},
89 }
90 }
91
92 Ok(())
93}
94
95pub struct GenericWhereGenerator<D: SqlDialect> {
126 dialect: D,
127 counter: ParamCounter,
128 indexed_columns: Option<Arc<HashSet<String>>>,
131}
132
133impl<D: SqlDialect> GenericWhereGenerator<D> {
134 pub const fn new(dialect: D) -> Self {
136 Self {
137 dialect,
138 counter: ParamCounter::new(),
139 indexed_columns: None,
140 }
141 }
142
143 #[must_use]
148 pub fn with_indexed_columns(mut self, cols: Arc<HashSet<String>>) -> Self {
149 self.indexed_columns = Some(cols);
150 self
151 }
152
153 pub fn generate(&self, clause: &WhereClause) -> Result<(String, Vec<serde_json::Value>)> {
160 self.generate_with_param_offset(clause, 0)
161 }
162
163 pub fn generate_with_hierarchy(
174 &self,
175 clause: &WhereClause,
176 hierarchy_ctx: &super::HierarchyContext,
177 ) -> Result<(String, Vec<serde_json::Value>)> {
178 self.counter.reset_to(0);
179 let mut params = Vec::new();
180 let sql = self.visit_impl(clause, &mut params, Some(hierarchy_ctx))?;
181 Ok((sql, params))
182 }
183
184 pub fn generate_with_param_offset(
194 &self,
195 clause: &WhereClause,
196 offset: usize,
197 ) -> Result<(String, Vec<serde_json::Value>)> {
198 self.counter.reset_to(offset);
199 let mut params = Vec::new();
200 let sql = self.visit(clause, &mut params)?;
201 Ok((sql, params))
202 }
203
204 fn visit(&self, clause: &WhereClause, params: &mut Vec<serde_json::Value>) -> Result<String> {
207 self.visit_impl(clause, params, None)
208 }
209
210 fn visit_impl(
211 &self,
212 clause: &WhereClause,
213 params: &mut Vec<serde_json::Value>,
214 hierarchy_ctx: Option<&super::HierarchyContext>,
215 ) -> Result<String> {
216 match clause {
217 WhereClause::And(clauses) => {
218 if clauses.is_empty() {
219 return Ok(self.dialect.always_true().to_string());
220 }
221 let parts: Result<Vec<_>> =
222 clauses.iter().map(|c| self.visit_impl(c, params, hierarchy_ctx)).collect();
223 Ok(format!("({})", parts?.join(" AND ")))
224 },
225 WhereClause::Or(clauses) => {
226 if clauses.is_empty() {
227 return Ok(self.dialect.always_false().to_string());
228 }
229 let parts: Result<Vec<_>> =
230 clauses.iter().map(|c| self.visit_impl(c, params, hierarchy_ctx)).collect();
231 Ok(format!("({})", parts?.join(" OR ")))
232 },
233 WhereClause::Not(inner) => {
234 Ok(format!("NOT ({})", self.visit_impl(inner, params, hierarchy_ctx)?))
235 },
236 WhereClause::Field {
237 path,
238 operator,
239 value,
240 } => self.visit_field(path, operator, value, params, hierarchy_ctx),
241 WhereClause::NativeField {
242 column,
243 pg_cast,
244 operator,
245 value,
246 } => self.visit_native_field(column, pg_cast, operator, value, params),
247 }
248 }
249
250 fn visit_native_field(
256 &self,
257 column: &str,
258 pg_cast: &str,
259 operator: &WhereOperator,
260 value: &serde_json::Value,
261 params: &mut Vec<serde_json::Value>,
262 ) -> Result<String> {
263 let col_expr = self.dialect.quote_identifier(column);
264 let p = self.push_param(params, value.clone());
265 let rhs = if pg_cast.is_empty() {
266 p
267 } else {
268 self.dialect.cast_native_param(&p, pg_cast)
269 };
270 match operator {
271 WhereOperator::Eq => Ok(format!("{col_expr} = {rhs}")),
272 WhereOperator::Neq => {
273 let neq = self.dialect.neq_operator();
274 Ok(format!("{col_expr} {neq} {rhs}"))
275 },
276 _ => Err(FraiseQLError::validation(format!(
277 "Operator {operator:?} is not supported for native column conditions"
278 ))),
279 }
280 }
281
282 fn resolve_field_expr(&self, path: &[String]) -> String {
285 if let Some(indexed) = &self.indexed_columns {
287 let col_name = path.join("__");
288 if indexed.contains(&col_name) {
289 return self.dialect.quote_identifier(&col_name);
290 }
291 }
292 self.dialect.json_extract_scalar("data", path)
293 }
294
295 fn push_param(&self, params: &mut Vec<serde_json::Value>, v: serde_json::Value) -> String {
298 params.push(v);
299 self.dialect.placeholder(self.counter.next())
300 }
301
302 fn visit_field(
305 &self,
306 path: &[String],
307 operator: &WhereOperator,
308 value: &serde_json::Value,
309 params: &mut Vec<serde_json::Value>,
310 hierarchy_ctx: Option<&super::HierarchyContext>,
311 ) -> Result<String> {
312 let field_expr = self.resolve_field_expr(path);
313
314 match operator {
315 WhereOperator::Eq => {
317 let p = self.push_param(params, value.clone());
318 if value.is_number() {
319 let cast = self.dialect.cast_to_numeric(&field_expr);
320 let rhs = self.dialect.cast_param_numeric(&p);
323 Ok(format!("{cast} = {rhs}"))
324 } else if value.is_boolean() {
325 let cast = self.dialect.cast_to_boolean(&field_expr);
326 Ok(format!("{cast} = {p}"))
327 } else {
328 Ok(format!("{field_expr} = {p}"))
329 }
330 },
331 WhereOperator::Neq => {
332 let p = self.push_param(params, value.clone());
333 let neq = self.dialect.neq_operator();
334 if value.is_number() {
335 let cast = self.dialect.cast_to_numeric(&field_expr);
336 let rhs = self.dialect.cast_param_numeric(&p);
337 Ok(format!("{cast} {neq} {rhs}"))
338 } else if value.is_boolean() {
339 let cast = self.dialect.cast_to_boolean(&field_expr);
340 Ok(format!("{cast} {neq} {p}"))
341 } else {
342 Ok(format!("{field_expr} {neq} {p}"))
343 }
344 },
345 WhereOperator::Gt | WhereOperator::Gte | WhereOperator::Lt | WhereOperator::Lte => {
346 let op = match operator {
347 WhereOperator::Gt => ">",
348 WhereOperator::Gte => ">=",
349 WhereOperator::Lt => "<",
350 _ => "<=",
351 };
352 let cast = self.dialect.cast_to_numeric(&field_expr);
353 let p = self.push_param(params, value.clone());
354 let rhs = self.dialect.cast_param_numeric(&p);
355 Ok(format!("{cast} {op} {rhs}"))
356 },
357
358 WhereOperator::In | WhereOperator::Nin => {
360 let arr = value.as_array().ok_or_else(|| {
361 FraiseQLError::validation("IN operator requires an array value".to_string())
362 })?;
363 if arr.is_empty() {
364 return Ok(if matches!(operator, WhereOperator::In) {
365 self.dialect.always_false().to_string()
366 } else {
367 self.dialect.always_true().to_string()
368 });
369 }
370 let placeholders: Vec<_> =
371 arr.iter().map(|v| self.push_param(params, v.clone())).collect();
372 let in_list = placeholders.join(", ");
373 let sql = format!("{field_expr} IN ({in_list})");
374 Ok(if matches!(operator, WhereOperator::Nin) {
375 format!("NOT ({sql})")
376 } else {
377 sql
378 })
379 },
380
381 WhereOperator::IsNull => {
383 let is_null = value.as_bool().unwrap_or(true);
384 let null_op = if is_null { "IS NULL" } else { "IS NOT NULL" };
385 Ok(format!("{field_expr} {null_op}"))
386 },
387
388 WhereOperator::Contains => {
390 let val_str = self.require_str(value, "Contains")?;
391 let escaped = escape_like_literal(val_str);
392 let p = self.push_param(params, serde_json::Value::String(escaped));
393 let pattern = self.dialect.concat_sql(&["'%'", &p, "'%'"]);
394 Ok(self.dialect.like_sql(&field_expr, &pattern))
395 },
396 WhereOperator::Icontains => {
397 let val_str = self.require_str(value, "Icontains")?;
398 let escaped = escape_like_literal(val_str);
399 let p = self.push_param(params, serde_json::Value::String(escaped));
400 let pattern = self.dialect.concat_sql(&["'%'", &p, "'%'"]);
401 Ok(self.dialect.ilike_sql(&field_expr, &pattern))
402 },
403 WhereOperator::Startswith => {
404 let val_str = self.require_str(value, "Startswith")?;
405 let escaped = escape_like_literal(val_str);
406 let p = self.push_param(params, serde_json::Value::String(escaped));
407 let pattern = self.dialect.concat_sql(&[&p, "'%'"]);
408 Ok(self.dialect.like_sql(&field_expr, &pattern))
409 },
410 WhereOperator::Istartswith => {
411 let val_str = self.require_str(value, "Istartswith")?;
412 let escaped = escape_like_literal(val_str);
413 let p = self.push_param(params, serde_json::Value::String(escaped));
414 let pattern = self.dialect.concat_sql(&[&p, "'%'"]);
415 Ok(self.dialect.ilike_sql(&field_expr, &pattern))
416 },
417 WhereOperator::Endswith => {
418 let val_str = self.require_str(value, "Endswith")?;
419 let escaped = escape_like_literal(val_str);
420 let p = self.push_param(params, serde_json::Value::String(escaped));
421 let pattern = self.dialect.concat_sql(&["'%'", &p]);
422 Ok(self.dialect.like_sql(&field_expr, &pattern))
423 },
424 WhereOperator::Iendswith => {
425 let val_str = self.require_str(value, "Iendswith")?;
426 let escaped = escape_like_literal(val_str);
427 let p = self.push_param(params, serde_json::Value::String(escaped));
428 let pattern = self.dialect.concat_sql(&["'%'", &p]);
429 Ok(self.dialect.ilike_sql(&field_expr, &pattern))
430 },
431 WhereOperator::Like => {
432 let p = self.push_param(params, value.clone());
433 Ok(self.dialect.like_sql(&field_expr, &p))
434 },
435 WhereOperator::Ilike => {
436 let p = self.push_param(params, value.clone());
437 Ok(self.dialect.ilike_sql(&field_expr, &p))
438 },
439 WhereOperator::Nlike => {
440 let p = self.push_param(params, value.clone());
441 Ok(format!("NOT ({})", self.dialect.like_sql(&field_expr, &p)))
442 },
443 WhereOperator::Nilike => {
444 let p = self.push_param(params, value.clone());
445 Ok(format!("NOT ({})", self.dialect.ilike_sql(&field_expr, &p)))
446 },
447
448 WhereOperator::Regex => {
450 if let Some(s) = value.as_str() {
451 validate_regex_pattern(s)?;
452 }
453 let p = self.push_param(params, value.clone());
454 self.dialect
455 .regex_sql(&field_expr, &p, false, false)
456 .map_err(|e| FraiseQLError::validation(e.to_string()))
457 },
458 WhereOperator::Iregex => {
459 if let Some(s) = value.as_str() {
460 validate_regex_pattern(s)?;
461 }
462 let p = self.push_param(params, value.clone());
463 self.dialect
464 .regex_sql(&field_expr, &p, true, false)
465 .map_err(|e| FraiseQLError::validation(e.to_string()))
466 },
467 WhereOperator::Nregex => {
468 if let Some(s) = value.as_str() {
469 validate_regex_pattern(s)?;
470 }
471 let p = self.push_param(params, value.clone());
472 self.dialect
473 .regex_sql(&field_expr, &p, false, true)
474 .map_err(|e| FraiseQLError::validation(e.to_string()))
475 },
476 WhereOperator::Niregex => {
477 if let Some(s) = value.as_str() {
478 validate_regex_pattern(s)?;
479 }
480 let p = self.push_param(params, value.clone());
481 self.dialect
482 .regex_sql(&field_expr, &p, true, true)
483 .map_err(|e| FraiseQLError::validation(e.to_string()))
484 },
485
486 WhereOperator::LenEq
488 | WhereOperator::LenNeq
489 | WhereOperator::LenGt
490 | WhereOperator::LenGte
491 | WhereOperator::LenLt
492 | WhereOperator::LenLte => {
493 let op = match operator {
494 WhereOperator::LenEq => "=",
495 WhereOperator::LenNeq => self.dialect.neq_operator(),
496 WhereOperator::LenGt => ">",
497 WhereOperator::LenGte => ">=",
498 WhereOperator::LenLt => "<",
499 _ => "<=",
500 };
501 let len_expr = self.dialect.json_array_length(&field_expr);
502 let p = self.push_param(params, value.clone());
503 Ok(format!("{len_expr} {op} {p}"))
504 },
505
506 WhereOperator::ArrayContains | WhereOperator::StrictlyContains => {
508 let p = self.push_param(params, value.clone());
511 self.dialect
512 .array_contains_sql(&field_expr, &p)
513 .map_err(|e| FraiseQLError::validation(e.to_string()))
514 },
515 WhereOperator::ArrayContainedBy => {
516 let p = self.push_param(params, value.clone());
517 self.dialect
518 .array_contained_by_sql(&field_expr, &p)
519 .map_err(|e| FraiseQLError::validation(e.to_string()))
520 },
521 WhereOperator::ArrayOverlaps => {
522 let p = self.push_param(params, value.clone());
523 self.dialect
524 .array_overlaps_sql(&field_expr, &p)
525 .map_err(|e| FraiseQLError::validation(e.to_string()))
526 },
527
528 WhereOperator::Matches => {
530 let p = self.push_param(params, value.clone());
531 self.dialect
532 .fts_matches_sql(&field_expr, &p)
533 .map_err(|e| FraiseQLError::validation(e.to_string()))
534 },
535 WhereOperator::PlainQuery => {
536 let p = self.push_param(params, value.clone());
537 self.dialect
538 .fts_plain_query_sql(&field_expr, &p)
539 .map_err(|e| FraiseQLError::validation(e.to_string()))
540 },
541 WhereOperator::PhraseQuery => {
542 let p = self.push_param(params, value.clone());
543 self.dialect
544 .fts_phrase_query_sql(&field_expr, &p)
545 .map_err(|e| FraiseQLError::validation(e.to_string()))
546 },
547 WhereOperator::WebsearchQuery => {
548 let p = self.push_param(params, value.clone());
549 self.dialect
550 .fts_websearch_query_sql(&field_expr, &p)
551 .map_err(|e| FraiseQLError::validation(e.to_string()))
552 },
553
554 WhereOperator::CosineDistance => {
556 let p = self.push_param(params, value.clone());
557 self.dialect
558 .vector_distance_sql("<=>", &field_expr, &p)
559 .map_err(|e| FraiseQLError::validation(e.to_string()))
560 },
561 WhereOperator::L2Distance => {
562 let p = self.push_param(params, value.clone());
563 self.dialect
564 .vector_distance_sql("<->", &field_expr, &p)
565 .map_err(|e| FraiseQLError::validation(e.to_string()))
566 },
567 WhereOperator::L1Distance => {
568 let p = self.push_param(params, value.clone());
569 self.dialect
570 .vector_distance_sql("<+>", &field_expr, &p)
571 .map_err(|e| FraiseQLError::validation(e.to_string()))
572 },
573 WhereOperator::HammingDistance => {
574 let p = self.push_param(params, value.clone());
575 self.dialect
576 .vector_distance_sql("<~>", &field_expr, &p)
577 .map_err(|e| FraiseQLError::validation(e.to_string()))
578 },
579 WhereOperator::InnerProduct => {
580 let p = self.push_param(params, value.clone());
581 self.dialect
582 .vector_distance_sql("<#>", &field_expr, &p)
583 .map_err(|e| FraiseQLError::validation(e.to_string()))
584 },
585 WhereOperator::JaccardDistance => {
586 let p = self.push_param(params, value.clone());
587 self.dialect
588 .jaccard_distance_sql(&field_expr, &p)
589 .map_err(|e| FraiseQLError::validation(e.to_string()))
590 },
591
592 WhereOperator::IsIPv4 => self
594 .dialect
595 .inet_check_sql(&field_expr, "IsIPv4")
596 .map_err(|e| FraiseQLError::validation(e.to_string())),
597 WhereOperator::IsIPv6 => self
598 .dialect
599 .inet_check_sql(&field_expr, "IsIPv6")
600 .map_err(|e| FraiseQLError::validation(e.to_string())),
601 WhereOperator::IsPrivate => {
602 let negate = value.as_bool().is_some_and(|v| !v);
603 let check_name = if negate { "IsPublic" } else { "IsPrivate" };
604 self.dialect
605 .inet_check_sql(&field_expr, check_name)
606 .map_err(|e| FraiseQLError::validation(e.to_string()))
607 },
608 WhereOperator::IsLoopback => {
609 let negate = value.as_bool().is_some_and(|v| !v);
610 let check_name = if negate {
611 "IsNotLoopback"
612 } else {
613 "IsLoopback"
614 };
615 self.dialect
616 .inet_check_sql(&field_expr, check_name)
617 .map_err(|e| FraiseQLError::validation(e.to_string()))
618 },
619 WhereOperator::IsMulticast => {
620 let negate = value.as_bool().is_some_and(|v| !v);
621 let check_name = if negate {
622 "IsNotMulticast"
623 } else {
624 "IsMulticast"
625 };
626 self.dialect
627 .inet_check_sql(&field_expr, check_name)
628 .map_err(|e| FraiseQLError::validation(e.to_string()))
629 },
630 WhereOperator::IsLinkLocal => {
631 let negate = value.as_bool().is_some_and(|v| !v);
632 let check_name = if negate {
633 "IsNotLinkLocal"
634 } else {
635 "IsLinkLocal"
636 };
637 self.dialect
638 .inet_check_sql(&field_expr, check_name)
639 .map_err(|e| FraiseQLError::validation(e.to_string()))
640 },
641 WhereOperator::IsDocumentation => {
642 let negate = value.as_bool().is_some_and(|v| !v);
643 let check_name = if negate {
644 "IsNotDocumentation"
645 } else {
646 "IsDocumentation"
647 };
648 self.dialect
649 .inet_check_sql(&field_expr, check_name)
650 .map_err(|e| FraiseQLError::validation(e.to_string()))
651 },
652 WhereOperator::IsCarrierGrade => {
653 let negate = value.as_bool().is_some_and(|v| !v);
654 let check_name = if negate {
655 "IsNotCarrierGrade"
656 } else {
657 "IsCarrierGrade"
658 };
659 self.dialect
660 .inet_check_sql(&field_expr, check_name)
661 .map_err(|e| FraiseQLError::validation(e.to_string()))
662 },
663 WhereOperator::InSubnet => {
664 let p = self.push_param(params, value.clone());
665 self.dialect
666 .inet_binary_sql("<<", &field_expr, &p)
667 .map_err(|e| FraiseQLError::validation(e.to_string()))
668 },
669 WhereOperator::ContainsSubnet | WhereOperator::ContainsIP => {
670 let p = self.push_param(params, value.clone());
671 self.dialect
672 .inet_binary_sql(">>", &field_expr, &p)
673 .map_err(|e| FraiseQLError::validation(e.to_string()))
674 },
675 WhereOperator::Overlaps => {
676 let p = self.push_param(params, value.clone());
677 self.dialect
678 .inet_binary_sql("&&", &field_expr, &p)
679 .map_err(|e| FraiseQLError::validation(e.to_string()))
680 },
681
682 WhereOperator::AncestorOf => {
684 let p = self.push_param(params, value.clone());
685 self.dialect
686 .ltree_binary_sql("@>", &field_expr, &p, "ltree")
687 .map_err(|e| FraiseQLError::validation(e.to_string()))
688 },
689 WhereOperator::DescendantOf => {
690 let p = self.push_param(params, value.clone());
691 self.dialect
692 .ltree_binary_sql("<@", &field_expr, &p, "ltree")
693 .map_err(|e| FraiseQLError::validation(e.to_string()))
694 },
695 WhereOperator::MatchesLquery => {
696 let p = self.push_param(params, value.clone());
697 self.dialect
698 .ltree_binary_sql("~", &field_expr, &p, "lquery")
699 .map_err(|e| FraiseQLError::validation(e.to_string()))
700 },
701 WhereOperator::MatchesLtxtquery => {
702 let p = self.push_param(params, value.clone());
703 self.dialect
704 .ltree_binary_sql("@", &field_expr, &p, "ltxtquery")
705 .map_err(|e| FraiseQLError::validation(e.to_string()))
706 },
707 WhereOperator::MatchesAnyLquery => {
708 let arr = value.as_array().ok_or_else(|| {
709 FraiseQLError::validation(
710 "matches_any_lquery operator requires an array value".to_string(),
711 )
712 })?;
713 if arr.is_empty() {
714 return Err(FraiseQLError::validation(
715 "matches_any_lquery requires at least one lquery".to_string(),
716 ));
717 }
718 let placeholders: Vec<_> = arr
719 .iter()
720 .map(|v| format!("{}::lquery", self.push_param(params, v.clone())))
721 .collect();
722 self.dialect
723 .ltree_any_lquery_sql(&field_expr, &placeholders)
724 .map_err(|e| FraiseQLError::validation(e.to_string()))
725 },
726 WhereOperator::DepthEq => {
727 let p = self.push_param(params, value.clone());
728 self.dialect
729 .ltree_depth_sql("=", &field_expr, &p)
730 .map_err(|e| FraiseQLError::validation(e.to_string()))
731 },
732 WhereOperator::DepthNeq => {
733 let p = self.push_param(params, value.clone());
734 self.dialect
735 .ltree_depth_sql("!=", &field_expr, &p)
736 .map_err(|e| FraiseQLError::validation(e.to_string()))
737 },
738 WhereOperator::DepthGt => {
739 let p = self.push_param(params, value.clone());
740 self.dialect
741 .ltree_depth_sql(">", &field_expr, &p)
742 .map_err(|e| FraiseQLError::validation(e.to_string()))
743 },
744 WhereOperator::DepthGte => {
745 let p = self.push_param(params, value.clone());
746 self.dialect
747 .ltree_depth_sql(">=", &field_expr, &p)
748 .map_err(|e| FraiseQLError::validation(e.to_string()))
749 },
750 WhereOperator::DepthLt => {
751 let p = self.push_param(params, value.clone());
752 self.dialect
753 .ltree_depth_sql("<", &field_expr, &p)
754 .map_err(|e| FraiseQLError::validation(e.to_string()))
755 },
756 WhereOperator::DepthLte => {
757 let p = self.push_param(params, value.clone());
758 self.dialect
759 .ltree_depth_sql("<=", &field_expr, &p)
760 .map_err(|e| FraiseQLError::validation(e.to_string()))
761 },
762 WhereOperator::Lca => {
763 let arr = value.as_array().ok_or_else(|| {
764 FraiseQLError::validation("lca operator requires an array value".to_string())
765 })?;
766 if arr.is_empty() {
767 return Err(FraiseQLError::validation(
768 "lca operator requires at least one path".to_string(),
769 ));
770 }
771 let placeholders: Vec<_> = arr
772 .iter()
773 .map(|v| format!("{}::ltree", self.push_param(params, v.clone())))
774 .collect();
775 self.dialect
776 .ltree_lca_sql(&field_expr, &placeholders)
777 .map_err(|e| FraiseQLError::validation(e.to_string()))
778 },
779
780 WhereOperator::DescendantOfId | WhereOperator::AncestorOfId => {
782 let ctx = hierarchy_ctx.ok_or_else(|| {
783 FraiseQLError::validation(
784 "descendantOfId/ancestorOfId requires HierarchyContext — \
785 configure [hierarchies] in fraiseql.toml"
786 .to_string(),
787 )
788 })?;
789 let pg_op = if matches!(operator, WhereOperator::DescendantOfId) {
790 "<@"
791 } else {
792 "@>"
793 };
794 let p = self.push_param(params, value.clone());
795 self.dialect
796 .ltree_id_subquery_sql(
797 pg_op,
798 &field_expr,
799 &ctx.table,
800 &ctx.path_column,
801 ctx.fk_column.as_deref(),
802 &p,
803 )
804 .map_err(|e| FraiseQLError::validation(e.to_string()))
805 },
806
807 WhereOperator::Extended(op) => {
809 self.dialect.generate_extended_sql(op, &field_expr, params)
810 },
811
812 #[allow(unreachable_patterns)]
817 _ => Err(FraiseQLError::Validation {
819 message: format!(
820 "Operator {operator:?} is not supported by the {} dialect",
821 self.dialect.name()
822 ),
823 path: None,
824 }),
825 }
826 }
827
828 fn require_str<'a>(&self, value: &'a serde_json::Value, op: &'static str) -> Result<&'a str> {
829 value.as_str().ok_or_else(|| {
830 FraiseQLError::validation(format!("{op} operator requires a string value"))
831 })
832 }
833}
834
835impl<D: SqlDialect + Default> Default for GenericWhereGenerator<D> {
838 fn default() -> Self {
839 Self::new(D::default())
840 }
841}
842
843impl<D: SqlDialect> crate::filters::ExtendedOperatorHandler for GenericWhereGenerator<D> {
847 fn generate_extended_sql(
848 &self,
849 operator: &crate::filters::ExtendedOperator,
850 field_sql: &str,
851 params: &mut Vec<serde_json::Value>,
852 ) -> Result<String> {
853 self.dialect.generate_extended_sql(operator, field_sql, params)
854 }
855}
856
857#[cfg(test)]
858mod tests;